1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2024-10-26 03:55:04 +03:00

globals: Also use thread-specific storage on "main" thread

Don't treat "main" thread specially. This simplifies access to
thread-specific data.

xmlGetGlobalState can now also fail on the former main thread, leading
to an unrecoverable condition if malloc fails.

The globals were never defined in public header files when compiling
with thread support. Now they're only defined in a legacy build.

Move TlsFree to DllMain to make cleanup more robust on Windows.

Obsoletes #1.
This commit is contained in:
Nick Wellnhofer 2024-07-15 23:23:06 +02:00
parent 769e5a4a42
commit 4f08a1a249
2 changed files with 128 additions and 96 deletions

187
globals.c
View File

@ -31,6 +31,11 @@
#include "private/threads.h"
#include "private/tree.h"
/*
* Mutex to protect "ForNewThreads" variables
*/
static xmlMutex xmlThrDefMutex;
/*
* Thread-local storage emulation.
*
@ -54,14 +59,7 @@
* function for static builds.
*/
/*
* Helpful Macro
*/
#ifdef LIBXML_THREAD_ENABLED
#define IS_MAIN_THREAD (xmlIsMainThreadInternal())
#else
#define IS_MAIN_THREAD 1
#endif
#define XML_DECLARE_MEMBER(name, type, attrs) \
type gs_##name;
@ -83,20 +81,13 @@ struct _xmlGlobalState {
#define XML_OP XML_DECLARE_MEMBER
XML_GLOBALS_ALLOC
XML_GLOBALS_ERROR
XML_GLOBALS_IO
XML_GLOBALS_PARSER
XML_GLOBALS_ERROR
XML_GLOBALS_TREE
XML_GLOBALS_IO
#undef XML_OP
};
/*
* Mutex to protect "ForNewThreads" variables
*/
static xmlMutex xmlThrDefMutex;
#ifdef LIBXML_THREAD_ENABLED
/*
* On Darwin, thread-local storage destructors seem to be run before
* pthread thread-specific data destructors. This causes ASan to
@ -124,14 +115,12 @@ static XML_THREAD_LOCAL xmlGlobalState globalState;
* thread exit.
*/
static pthread_key_t globalkey;
static pthread_t mainthread;
#elif defined HAVE_WIN32_THREADS
#ifndef USE_TLS
static DWORD globalkey = TLS_OUT_OF_INDEXES;
#endif
static DWORD mainthread;
#endif /* HAVE_WIN32_THREADS */
@ -146,10 +135,6 @@ xmlFreeGlobalState(void *state);
* *
************************************************************************/
#ifdef LIBXML_THREAD_ENABLED
static unsigned xmlMainThreadRngState[2];
#endif
/*
* Memory allocation routines
*/
@ -255,7 +240,9 @@ const int xmlParserDebugEntities = 0;
* Global setting, indicate that the parser should work in validating mode.
* Disabled by default.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlDoValidityCheckingDefaultValue = 0;
#endif
static int xmlDoValidityCheckingDefaultValueThrDef = 0;
/**
* xmlGetWarningsDefaultValue:
@ -265,7 +252,9 @@ static int xmlDoValidityCheckingDefaultValueThrDef = 0;
* Global setting, indicate that the DTD validation should provide warnings.
* Activated by default.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlGetWarningsDefaultValue = 1;
#endif
static int xmlGetWarningsDefaultValueThrDef = 1;
/**
* xmlLoadExtDtdDefaultValue:
@ -276,7 +265,9 @@ static int xmlGetWarningsDefaultValueThrDef = 1;
* validating.
* Disabled by default.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlLoadExtDtdDefaultValue = 0;
#endif
static int xmlLoadExtDtdDefaultValueThrDef = 0;
/**
* xmlPedanticParserDefaultValue:
@ -286,7 +277,9 @@ static int xmlLoadExtDtdDefaultValueThrDef = 0;
* Global setting, indicate that the parser be pedantic
* Disabled by default.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlPedanticParserDefaultValue = 0;
#endif
static int xmlPedanticParserDefaultValueThrDef = 0;
/**
* xmlLineNumbersDefaultValue:
@ -298,7 +291,9 @@ static int xmlPedanticParserDefaultValueThrDef = 0;
* Disabled by default since this may not be safe for old classes of
* application.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlLineNumbersDefaultValue = 0;
#endif
static int xmlLineNumbersDefaultValueThrDef = 0;
/**
* xmlKeepBlanksDefaultValue:
@ -311,7 +306,9 @@ static int xmlLineNumbersDefaultValueThrDef = 0;
* conformant to the XML Recommendation, however the option is kept
* for some applications since this was libxml1 default behaviour.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlKeepBlanksDefaultValue = 1;
#endif
static int xmlKeepBlanksDefaultValueThrDef = 1;
/**
* xmlSubstituteEntitiesDefaultValue:
@ -324,7 +321,9 @@ static int xmlKeepBlanksDefaultValueThrDef = 1;
* the XPath data model requires entities replacement and the XPath
* engine does not handle entities references transparently.
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlSubstituteEntitiesDefaultValue = 0;
#endif
static int xmlSubstituteEntitiesDefaultValueThrDef = 0;
/**
@ -332,7 +331,9 @@ static int xmlSubstituteEntitiesDefaultValueThrDef = 0;
*
* DEPRECATED: Don't use
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlRegisterNodeFunc xmlRegisterNodeDefaultValue = NULL;
#endif
static xmlRegisterNodeFunc xmlRegisterNodeDefaultValueThrDef = NULL;
/**
@ -340,7 +341,9 @@ static xmlRegisterNodeFunc xmlRegisterNodeDefaultValueThrDef = NULL;
*
* DEPRECATED: Don't use
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlDeregisterNodeFunc xmlDeregisterNodeDefaultValue = NULL;
#endif
static xmlDeregisterNodeFunc xmlDeregisterNodeDefaultValueThrDef = NULL;
/**
@ -348,16 +351,23 @@ static xmlDeregisterNodeFunc xmlDeregisterNodeDefaultValueThrDef = NULL;
*
* DEPRECATED: Don't use
*/
xmlParserInputBufferCreateFilenameFunc xmlParserInputBufferCreateFilenameValue = NULL;
static xmlParserInputBufferCreateFilenameFunc xmlParserInputBufferCreateFilenameValueThrDef = NULL;
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlParserInputBufferCreateFilenameFunc
xmlParserInputBufferCreateFilenameValue = NULL;
#endif
static xmlParserInputBufferCreateFilenameFunc
xmlParserInputBufferCreateFilenameValueThrDef = NULL;
/**
* xmlOutputBufferCreateFilenameValue:
*
* DEPRECATED: Don't use
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlOutputBufferCreateFilenameFunc xmlOutputBufferCreateFilenameValue = NULL;
static xmlOutputBufferCreateFilenameFunc xmlOutputBufferCreateFilenameValueThrDef = NULL;
#endif
static xmlOutputBufferCreateFilenameFunc
xmlOutputBufferCreateFilenameValueThrDef = NULL;
/**
* xmlGenericError:
@ -366,7 +376,9 @@ static xmlOutputBufferCreateFilenameFunc xmlOutputBufferCreateFilenameValueThrDe
*
* Global setting: function used for generic error callbacks
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlGenericErrorFunc xmlGenericError = xmlGenericErrorDefaultFunc;
#endif
static xmlGenericErrorFunc xmlGenericErrorThrDef = xmlGenericErrorDefaultFunc;
/**
* xmlStructuredError:
@ -375,7 +387,9 @@ static xmlGenericErrorFunc xmlGenericErrorThrDef = xmlGenericErrorDefaultFunc;
*
* Global setting: function used for structured error callbacks
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlStructuredErrorFunc xmlStructuredError = NULL;
#endif
static xmlStructuredErrorFunc xmlStructuredErrorThrDef = NULL;
/**
* xmlGenericErrorContext:
@ -384,7 +398,9 @@ static xmlStructuredErrorFunc xmlStructuredErrorThrDef = NULL;
*
* Global setting passed to generic error callbacks
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
void *xmlGenericErrorContext = NULL;
#endif
static void *xmlGenericErrorContextThrDef = NULL;
/**
* xmlStructuredErrorContext:
@ -393,9 +409,13 @@ static void *xmlGenericErrorContextThrDef = NULL;
*
* Global setting passed to structured error callbacks
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
void *xmlStructuredErrorContext = NULL;
#endif
static void *xmlStructuredErrorContextThrDef = NULL;
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
xmlError xmlLastError;
#endif
#ifdef LIBXML_OUTPUT_ENABLED
/*
@ -409,7 +429,9 @@ xmlError xmlLastError;
* Global setting, asking the serializer to indent the output tree by default
* Enabled by default
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlIndentTreeOutput = 1;
#endif
static int xmlIndentTreeOutputThrDef = 1;
/**
@ -420,7 +442,9 @@ static int xmlIndentTreeOutputThrDef = 1;
* The string used to do one-level indent. By default is equal to
* " " (two spaces)
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
const char *xmlTreeIndentString = " ";
#endif
static const char *xmlTreeIndentStringThrDef = " ";
/**
@ -433,7 +457,9 @@ static const char *xmlTreeIndentStringThrDef = " ";
* once parsed.
* Disabled by default
*/
#if !defined(LIBXML_THREAD_ENABLED) || defined(LIBXML_LEGACY_ENABLED)
int xmlSaveNoEmptyTags = 0;
#endif
static int xmlSaveNoEmptyTagsThrDef = 0;
#endif /* LIBXML_OUTPUT_ENABLED */
@ -559,17 +585,11 @@ void xmlInitGlobalsInternal(void) {
#ifdef HAVE_POSIX_THREADS
pthread_key_create(&globalkey, xmlFreeGlobalState);
mainthread = pthread_self();
#elif defined(HAVE_WIN32_THREADS)
#ifndef USE_TLS
globalkey = TlsAlloc();
if (globalkey == TLS_OUT_OF_INDEXES)
globalkey = TlsAlloc();
#endif
mainthread = GetCurrentThreadId();
#endif
#ifdef LIBXML_THREAD_ENABLED
xmlMainThreadRngState[0] = xmlGlobalRandom();
xmlMainThreadRngState[1] = xmlGlobalRandom();
#endif
}
@ -590,20 +610,34 @@ void xmlCleanupGlobals(void) {
* Additional cleanup for multi-threading
*/
void xmlCleanupGlobalsInternal(void) {
xmlResetError(&xmlLastError);
xmlCleanupMutex(&xmlThrDefMutex);
/*
* We assume that all other threads using the library have
* terminated and the last remaining thread calls
* xmlCleanupParser.
*/
#ifdef HAVE_POSIX_THREADS
/*
* Free thread-specific data of last thread before calling
* pthread_key_delete.
*/
xmlGlobalState *gs = pthread_getspecific(globalkey);
if (gs != NULL)
xmlFreeGlobalState(gs);
pthread_key_delete(globalkey);
#elif defined(HAVE_WIN32_THREADS)
#ifndef USE_TLS
#if !defined(USE_TLS) && \
defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL)
if (globalkey != TLS_OUT_OF_INDEXES) {
TlsFree(globalkey);
globalkey = TLS_OUT_OF_INDEXES;
}
#endif
#else /* no thread support */
xmlResetError(&xmlLastError);
#endif
xmlCleanupMutex(&xmlThrDefMutex);
}
/**
@ -630,22 +664,6 @@ xmlGetGlobalState(void)
return(NULL);
}
static int
xmlIsMainThreadInternal(void) {
/*
* Make sure that mainthread is initialized.
*/
xmlInitParser();
#ifdef HAVE_POSIX_THREADS
return (pthread_equal(mainthread, pthread_self()));
#elif defined HAVE_WIN32_THREADS
return (mainthread == GetCurrentThreadId());
#else
return (1);
#endif
}
/**
* xmlIsMainThread:
*
@ -657,7 +675,7 @@ xmlIsMainThreadInternal(void) {
*/
int
xmlIsMainThread(void) {
return(xmlIsMainThreadInternal());
return(0);
}
#ifdef LIBXML_THREAD_ENABLED
@ -719,42 +737,49 @@ xmlRegisterGlobalStateDtor(xmlGlobalState *gs) {
static void
xmlInitGlobalState(xmlGlobalStatePtr gs) {
xmlMutexLock(&xmlThrDefMutex);
gs->localRngState[0] = xmlGlobalRandom();
gs->localRngState[1] = xmlGlobalRandom();
memset(&gs->lastError, 0, sizeof(xmlError));
gs->gs_xmlDoValidityCheckingDefaultValue =
xmlDoValidityCheckingDefaultValueThrDef;
#ifdef LIBXML_THREAD_ALLOC_ENABLED
/* XML_GLOBALS_ALLOC */
gs->gs_xmlFree = free;
gs->gs_xmlMalloc = malloc;
gs->gs_xmlMallocAtomic = malloc;
gs->gs_xmlRealloc = realloc;
gs->gs_xmlMemStrdup = xmlPosixStrdup;
#endif
xmlMutexLock(&xmlThrDefMutex);
/* XML_GLOBALS_PARSER */
gs->gs_xmlDoValidityCheckingDefaultValue =
xmlDoValidityCheckingDefaultValueThrDef;
gs->gs_xmlGetWarningsDefaultValue = xmlGetWarningsDefaultValueThrDef;
#ifdef LIBXML_OUTPUT_ENABLED
gs->gs_xmlIndentTreeOutput = xmlIndentTreeOutputThrDef;
gs->gs_xmlTreeIndentString = xmlTreeIndentStringThrDef;
gs->gs_xmlSaveNoEmptyTags = xmlSaveNoEmptyTagsThrDef;
#endif
gs->gs_xmlKeepBlanksDefaultValue = xmlKeepBlanksDefaultValueThrDef;
gs->gs_xmlLineNumbersDefaultValue = xmlLineNumbersDefaultValueThrDef;
gs->gs_xmlLoadExtDtdDefaultValue = xmlLoadExtDtdDefaultValueThrDef;
gs->gs_xmlPedanticParserDefaultValue = xmlPedanticParserDefaultValueThrDef;
gs->gs_xmlSubstituteEntitiesDefaultValue =
xmlSubstituteEntitiesDefaultValueThrDef;
#ifdef LIBXML_OUTPUT_ENABLED
gs->gs_xmlIndentTreeOutput = xmlIndentTreeOutputThrDef;
gs->gs_xmlTreeIndentString = xmlTreeIndentStringThrDef;
gs->gs_xmlSaveNoEmptyTags = xmlSaveNoEmptyTagsThrDef;
#endif
/* XML_GLOBALS_ERROR */
gs->gs_xmlGenericError = xmlGenericErrorThrDef;
gs->gs_xmlStructuredError = xmlStructuredErrorThrDef;
gs->gs_xmlGenericErrorContext = xmlGenericErrorContextThrDef;
gs->gs_xmlStructuredErrorContext = xmlStructuredErrorContextThrDef;
/* XML_GLOBALS_TREE */
gs->gs_xmlRegisterNodeDefaultValue = xmlRegisterNodeDefaultValueThrDef;
gs->gs_xmlDeregisterNodeDefaultValue = xmlDeregisterNodeDefaultValueThrDef;
/* XML_GLOBALS_IO */
gs->gs_xmlParserInputBufferCreateFilenameValue =
xmlParserInputBufferCreateFilenameValueThrDef;
gs->gs_xmlOutputBufferCreateFilenameValue =
@ -791,6 +816,10 @@ xmlNewGlobalState(int allowFailure)
{
xmlGlobalState *gs;
/*
* We use malloc/free to allow accessing globals before setting
* custom memory allocators.
*/
gs = malloc(sizeof(xmlGlobalState));
if (gs == NULL) {
if (allowFailure)
@ -817,6 +846,8 @@ xmlGetThreadLocalStorage(int allowFailure) {
(void) allowFailure;
xmlInitParser();
#ifdef USE_TLS
gs = &globalState;
if (gs->initialized == 0)
@ -840,26 +871,20 @@ xmlGetThreadLocalStorage(int allowFailure) {
#define XML_DEFINE_GLOBAL_WRAPPER(name, type, attrs) \
type *__##name(void) { \
if (IS_MAIN_THREAD) \
return (&name); \
else \
return (&xmlGetThreadLocalStorage(0)->gs_##name); \
return (&xmlGetThreadLocalStorage(0)->gs_##name); \
}
#define XML_OP XML_DEFINE_GLOBAL_WRAPPER
XML_GLOBALS_ALLOC
XML_GLOBALS_ERROR
XML_GLOBALS_IO
XML_GLOBALS_PARSER
XML_GLOBALS_ERROR
XML_GLOBALS_TREE
XML_GLOBALS_IO
#undef XML_OP
const xmlError *
__xmlLastError(void) {
if (IS_MAIN_THREAD)
return(&xmlLastError);
else
return(&xmlGetThreadLocalStorage(0)->lastError);
return(&xmlGetThreadLocalStorage(0)->lastError);
}
/**
@ -869,10 +894,7 @@ __xmlLastError(void) {
*/
unsigned *
xmlGetLocalRngState(void) {
if (IS_MAIN_THREAD)
return(xmlMainThreadRngState);
else
return(xmlGetThreadLocalStorage(0)->localRngState);
return(xmlGetThreadLocalStorage(0)->localRngState);
}
/* For backward compatibility */
@ -938,7 +960,7 @@ __htmlDefaultSAXHandler(void) {
int
xmlCheckThreadLocalStorage(void) {
#if defined(LIBXML_THREAD_ENABLED) && !defined(USE_TLS)
if ((!xmlIsMainThreadInternal()) && (xmlGetThreadLocalStorage(1) == NULL))
if (xmlGetThreadLocalStorage(1) == NULL)
return(-1);
#endif
return(0);
@ -947,10 +969,7 @@ xmlCheckThreadLocalStorage(void) {
xmlError *
xmlGetLastErrorInternal(void) {
#ifdef LIBXML_THREAD_ENABLED
if (IS_MAIN_THREAD)
return(&xmlLastError);
else
return(&xmlGetThreadLocalStorage(0)->lastError);
return(&xmlGetThreadLocalStorage(0)->lastError);
#else
return(&xmlLastError);
#endif
@ -1012,6 +1031,10 @@ DllMain(ATTRIBUTE_UNUSED HINSTANCE hinstDLL, DWORD fdwReason,
case DLL_PROCESS_DETACH:
if (xmlFree == free)
xmlCleanupParser();
if (globalkey != TLS_OUT_OF_INDEXES) {
TlsFree(globalkey);
globalkey = TLS_OUT_OF_INDEXES;
}
break;
#endif
}

View File

@ -427,8 +427,12 @@ xmlInitParserWinWrapper(INIT_ONCE *initOnce ATTRIBUTE_UNUSED,
*
* Initialization function for the XML parser.
*
* Call once from the main thread before using the library in
* multithreaded programs.
* For older versions, it's recommended to call this function once
* from the main thread before using the library in multithreaded
* programs.
*
* Since 2.14.0, there's no distinction between threads. It should
* be unnecessary to call this function.
*/
void
xmlInitParser(void) {
@ -448,23 +452,29 @@ xmlInitParser(void) {
* xmlCleanupParser:
*
* This function is named somewhat misleadingly. It does not clean up
* parser state but global memory allocated by the library itself. This
* function is mainly useful to avoid false positives from memory leak
* checkers and SHOULD ONLY BE CALLED RIGHT BEFORE THE WHOLE PROCESS
* EXITS.
* parser state but global memory allocated by the library itself.
*
* WARNING: If a process is multithreaded or uses other shared or
* dynamic libraries, calling this function may cause crashes if
* another thread or library is still using libxml2. It can be very
* hard to guess if libxml2 is in use by a process. In case of doubt
* abstain from calling this function.
* Since 2.9.11, cleanup is performed automatically if a shared or
* dynamic libxml2 library is unloaded. This function should only
* be used to avoid false positives from memory leak checkers in
* static builds.
*
* WARNING: xmlCleanupParser assumes that all other threads that called
* libxml2 functions have terminated. No library calls must be made
* after calling this function. In general, THIS FUNCTION SHOULD ONLY
* BE CALLED RIGHT BEFORE THE WHOLE PROCESS EXITS.
*/
void
xmlCleanupParser(void) {
/*
* Unfortunately, some users call this function to fix memory
* leaks on unload with versions before 2.9.11. This can result
* in the library being reinitialized, so this use case must
* be supported.
*/
if (!xmlParserInitialized)
return;
/* These functions can call xmlFree. */
xmlCleanupCharEncodingHandlers();
#ifdef LIBXML_CATALOG_ENABLED
xmlCatalogCleanup();
@ -475,14 +485,13 @@ xmlCleanupParser(void) {
xmlRelaxNGCleanupTypes();
#endif
/* These functions should never call xmlFree. */
xmlCleanupDictInternal();
xmlCleanupRandom();
xmlCleanupGlobalsInternal();
xmlCleanupThreadsInternal();
/*
* Must come last. xmlCleanupGlobalsInternal can call xmlFree which
* Must come after all cleanup functions that call xmlFree which
* uses xmlMemMutex in debug mode.
*/
xmlCleanupMemoryInternal();