From a76de5e0395f9e402f3b787a7035bbe2fa938b1a Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Fri, 1 Sep 2023 15:09:48 +0200 Subject: [PATCH 01/10] gconstructor.h: Drop support for Visual C++ 2005 It's not supported anymore, remove dedicated code --- glib/gconstructor.h | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/glib/gconstructor.h b/glib/gconstructor.h index 29509e4a4..3a6b9dfd8 100644 --- a/glib/gconstructor.h +++ b/glib/gconstructor.h @@ -54,8 +54,7 @@ #define G_DEFINE_CONSTRUCTOR(_func) static void __attribute__((constructor)) _func (void); #define G_DEFINE_DESTRUCTOR(_func) static void __attribute__((destructor)) _func (void); -#elif defined (_MSC_VER) && (_MSC_VER >= 1500) -/* Visual studio 2008 and later has _Pragma */ +#elif defined (_MSC_VER) /* * Only try to include gslist.h if not already included via glib.h, @@ -108,28 +107,6 @@ __pragma(section(".CRT$XCU",read)) \ __declspec(allocate(".CRT$XCU")) int (* _array ## _func)(void) = _func ## _constructor; -#elif defined (_MSC_VER) - -#define G_HAS_CONSTRUCTORS 1 - -/* Pre Visual studio 2008 must use #pragma section */ -#define G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA 1 -#define G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA 1 - -#define G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(_func) \ - section(".CRT$XCU",read) -#define G_DEFINE_CONSTRUCTOR(_func) \ - static void _func(void); \ - static int _func ## _wrapper(void) { _func(); return 0; } \ - __declspec(allocate(".CRT$XCU")) static int (*p)(void) = _func ## _wrapper; - -#define G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(_func) \ - section(".CRT$XCU",read) -#define G_DEFINE_DESTRUCTOR(_func) \ - static void _func(void); \ - static int _func ## _constructor(void) { atexit (_func); return 0; } \ - __declspec(allocate(".CRT$XCU")) static int (* _array ## _func)(void) = _func ## _constructor; - #elif defined(__SUNPRO_C) /* This is not tested, but i believe it should work, based on: From 51cbb63cd5c2c168603d3161887294e2d85f5fcb Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 31 Aug 2023 21:51:58 +0200 Subject: [PATCH 02/10] Add support for TLS callbacks on Windows --- docs/reference/glib/meson.build | 1 + glib/gconstructorprivate.h | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 glib/gconstructorprivate.h diff --git a/docs/reference/glib/meson.build b/docs/reference/glib/meson.build index 3cfff2f0b..fe34e3fff 100644 --- a/docs/reference/glib/meson.build +++ b/docs/reference/glib/meson.build @@ -34,6 +34,7 @@ if get_option('gtk_doc') 'gtranslit-data.h', 'glib-init.h', 'gconstructor.h', + 'gconstructorprivate.h', 'valgrind.h', 'gutilsprivate.h', 'gvalgrind.h', diff --git a/glib/gconstructorprivate.h b/glib/gconstructorprivate.h new file mode 100644 index 000000000..c3c43bbe9 --- /dev/null +++ b/glib/gconstructorprivate.h @@ -0,0 +1,55 @@ +/* + * Copyright © 2023 Luca Bacci + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "gconstructor.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef _WIN32 + +#ifdef _MSC_VER + +#define G_HAS_TLS_CALLBACKS 1 + +#define G_DEFINE_TLS_CALLBACK(func) \ +__pragma (section (".CRT$XLCE", long, read)) \ + \ +static void NTAPI func (PVOID, DWORD, PVOID); \ + \ +__declspec (allocate (".CRT$XLCE")) \ +const PIMAGE_TLS_CALLBACK _ptr_##func = func; \ + \ +__pragma (comment (linker, "/INCLUDE:" G_MSVC_SYMBOL_PREFIX "_tls_used")) \ +__pragma (comment (linker, "/INCLUDE:" G_MSVC_SYMBOL_PREFIX "_ptr_" #func)) + +#else + +#define G_HAS_TLS_CALLBACKS 1 + +#define G_DEFINE_TLS_CALLBACK(func) \ +static void NTAPI func (PVOID, DWORD, PVOID); \ + \ +__attribute__((section(".CRT$XLCE"))) \ +const PIMAGE_TLS_CALLBACK _ptr_##func = func; + +#endif /* _MSC_VER */ + +#endif /* _WIN32 */ From 8436785a5f58a4ce925fa4a801bcaef6f1e4dd89 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 31 Aug 2023 21:54:06 +0200 Subject: [PATCH 03/10] Make use of TLS callbacks for static builds on Windows Fixes https://gitlab.gnome.org/GNOME/glib/-/issues/3087 --- glib/glib-init.c | 52 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/glib/glib-init.c b/glib/glib-init.c index 933f891da..e9017942f 100644 --- a/glib/glib-init.c +++ b/glib/glib-init.c @@ -26,6 +26,7 @@ #include "gtypes.h" #include "gutils.h" /* for GDebugKey */ #include "gconstructor.h" +#include "gconstructorprivate.h" #include "gmem.h" /* for g_mem_gc_friendly */ #include @@ -448,32 +449,55 @@ DllMain (HINSTANCE hinstDLL, return TRUE; } -#elif defined(G_HAS_CONSTRUCTORS) /* && G_PLATFORM_WIN32 && GLIB_STATIC_COMPILATION */ -#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA -#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(glib_init_ctor) -#endif -#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA -#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS(glib_init_dtor) +#else + +#ifndef G_HAS_CONSTRUCTORS +#error static compilation on Windows requires constructor support #endif -G_DEFINE_CONSTRUCTOR (glib_init_ctor) +#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA +#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(glib_priv_constructor) +#endif + +static gboolean tls_callback_invoked; + +G_DEFINE_CONSTRUCTOR (glib_priv_constructor) static void -glib_init_ctor (void) +glib_priv_constructor (void) { glib_win32_init (); + + if (!tls_callback_invoked) + g_critical ("TLS callback not invoked"); } -G_DEFINE_DESTRUCTOR (glib_init_dtor) +#ifndef G_HAS_TLS_CALLBACKS +#error static compilation on Windows requires TLS callbacks support +#endif -static void -glib_init_dtor (void) +G_DEFINE_TLS_CALLBACK (glib_priv_tls_callback) + +static void NTAPI +glib_priv_tls_callback (LPVOID hinstance, + DWORD reason, + LPVOID reserved) { - glib_win32_deinit (FALSE); + switch (reason) + { + case DLL_PROCESS_ATTACH: + glib_dll = hinstance; + tls_callback_invoked = TRUE; + break; + case DLL_PROCESS_DETACH: + glib_win32_deinit (reserved == NULL); + break; + + default: + break; + } } -#else /* G_PLATFORM_WIN32 && GLIB_STATIC_COMPILATION && !G_HAS_CONSTRUCTORS */ -#error Your platform/compiler is missing constructor support #endif /* GLIB_STATIC_COMPILATION */ #elif defined(G_HAS_CONSTRUCTORS) /* && !G_PLATFORM_WIN32 */ From f916621d72b9804a3ef4bd83e485fe81bb876dab Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 31 Aug 2023 21:58:17 +0200 Subject: [PATCH 04/10] gthread-win32: Clean up GPrivate data in all cases --- glib/glib-init.c | 5 +++++ glib/gthread-win32.c | 22 ---------------------- glib/tests/private.c | 13 ------------- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/glib/glib-init.c b/glib/glib-init.c index e9017942f..7d4a4d5d9 100644 --- a/glib/glib-init.c +++ b/glib/glib-init.c @@ -489,6 +489,11 @@ glib_priv_tls_callback (LPVOID hinstance, glib_dll = hinstance; tls_callback_invoked = TRUE; break; + case DLL_THREAD_DETACH: +#ifdef THREADS_WIN32 + g_thread_win32_thread_detach (); +#endif + break; case DLL_PROCESS_DETACH: glib_win32_deinit (reserved == NULL); break; diff --git a/glib/gthread-win32.c b/glib/gthread-win32.c index 58e244ebe..4d768eb67 100644 --- a/glib/gthread-win32.c +++ b/glib/gthread-win32.c @@ -424,28 +424,6 @@ g_system_thread_free (GRealThread *thread) void g_system_thread_exit (void) { - /* In static compilation, DllMain doesn't exist and so DLL_THREAD_DETACH - * case is never called and thread destroy notifications are not triggered. - * To ensure that notifications are correctly triggered in static - * compilation mode, we call directly the "detach" function here right - * before terminating the thread. - * As all win32 threads initialized through the glib API are run through - * the same proxy function g_thread_win32_proxy() which calls systematically - * g_system_thread_exit() when finishing, we obtain the same behavior as - * with dynamic compilation. - * - * WARNING: unfortunately this mechanism cannot work with threads created - * directly from the Windows API using CreateThread() or _beginthread/ex(). - * It only works with threads created by using the glib API with - * g_system_thread_new(). If users need absolutely to use a thread NOT - * created with glib API under Windows and in static compilation mode, they - * should not use glib functions within their thread or they may encounter - * memory leaks when the thread finishes. - */ -#ifdef GLIB_STATIC_COMPILATION - g_thread_win32_thread_detach (); -#endif - _endthreadex (0); } diff --git a/glib/tests/private.c b/glib/tests/private.c index 37f7761a0..424c34b86 100644 --- a/glib/tests/private.c +++ b/glib/tests/private.c @@ -148,19 +148,6 @@ test_private3 (void) thread = (HANDLE) _beginthreadex (NULL, 0, private3_func, NULL, 0, &ignore); WaitForSingleObject (thread, INFINITE); CloseHandle (thread); - - /* FIXME: with static compilation on Windows this test will fail because - * it is mixing up glib threads with Microsoft native thread API. See - * comment in gthread-win32.c for g_system_thread_exit() implementation. - * Fix is not straightforward, possible solution could be to use FLS - * functions (instead of TLS) as proposed in - * https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1655 - */ - if (!private3_freed) - { - g_test_skip ("FIXME: GPrivate with native win32 thread"); - return; - } } #else { From 0bbe3c8447d2a251d48da2d9bf934ab642e965d3 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Fri, 1 Sep 2023 11:20:21 +0200 Subject: [PATCH 05/10] gconstructorprivate.h: Add seamless support for C++ G_DEFINE_TLS_CALLBACK could be made public in the future --- glib/gconstructorprivate.h | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/glib/gconstructorprivate.h b/glib/gconstructorprivate.h index c3c43bbe9..1c8e64ce6 100644 --- a/glib/gconstructorprivate.h +++ b/glib/gconstructorprivate.h @@ -25,6 +25,16 @@ #ifdef _WIN32 +#ifdef __cplusplus +/* const defaults to static (internal visibility) in C++, + * but we want extern instead */ +#define G_EXTERN_CONST extern const +#else +/* Using extern const in C is perfectly valid, but triggers + * a warning in GCC and CLANG, therefore we avoid it */ +#define G_EXTERN_CONST const +#endif + #ifdef _MSC_VER #define G_HAS_TLS_CALLBACKS 1 @@ -34,8 +44,10 @@ __pragma (section (".CRT$XLCE", long, read)) \ \ static void NTAPI func (PVOID, DWORD, PVOID); \ \ +G_BEGIN_DECLS \ __declspec (allocate (".CRT$XLCE")) \ -const PIMAGE_TLS_CALLBACK _ptr_##func = func; \ +G_EXTERN_CONST PIMAGE_TLS_CALLBACK _ptr_##func = func; \ +G_END_DECLS \ \ __pragma (comment (linker, "/INCLUDE:" G_MSVC_SYMBOL_PREFIX "_tls_used")) \ __pragma (comment (linker, "/INCLUDE:" G_MSVC_SYMBOL_PREFIX "_ptr_" #func)) @@ -44,11 +56,13 @@ __pragma (comment (linker, "/INCLUDE:" G_MSVC_SYMBOL_PREFIX "_ptr_" #func)) #define G_HAS_TLS_CALLBACKS 1 -#define G_DEFINE_TLS_CALLBACK(func) \ -static void NTAPI func (PVOID, DWORD, PVOID); \ - \ -__attribute__((section(".CRT$XLCE"))) \ -const PIMAGE_TLS_CALLBACK _ptr_##func = func; +#define G_DEFINE_TLS_CALLBACK(func) \ +static void NTAPI func (PVOID, DWORD, PVOID); \ + \ +G_BEGIN_DECLS \ +__attribute__ ((section (".CRT$XLCE"))) \ +G_EXTERN_CONST PIMAGE_TLS_CALLBACK _ptr_##func = func; \ +G_END_DECLS #endif /* _MSC_VER */ From af0a9489e70d39cb5c0238dac5d8cfb5f809f958 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Fri, 1 Sep 2023 16:39:38 +0200 Subject: [PATCH 06/10] Docs: Static builds on MSVC are supported --- docs/supported-platforms.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/supported-platforms.md b/docs/supported-platforms.md index ef627de19..74dd01d1a 100644 --- a/docs/supported-platforms.md +++ b/docs/supported-platforms.md @@ -23,8 +23,6 @@ their vendor. * Windows: [minimum version is Windows 8](https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1970), minimum build chain is Visual Studio 2012 - * Static builds are only supported with MinGW-based toolchains (cf - [this comment](https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2384#note_1336662)) * Android: [minimum NDK version 15](https://gitlab.gnome.org/GNOME/glib/issues/1113) * Linux: glibc newer than 2.5 (if using glibc; other forms of libc are supported) From 683991d83670a5cd88d26c3cbaa418ec65f37163 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 31 Aug 2023 21:52:41 +0200 Subject: [PATCH 07/10] Add test for module constructors, destructors and TLS callbacks --- glib/tests/constructor.c | 251 +++++++++++++++++++++++++++++++++++++++ glib/tests/meson.build | 14 +++ 2 files changed, 265 insertions(+) create mode 100644 glib/tests/constructor.c diff --git a/glib/tests/constructor.c b/glib/tests/constructor.c new file mode 100644 index 000000000..029f5a110 --- /dev/null +++ b/glib/tests/constructor.c @@ -0,0 +1,251 @@ +/* constructor.c - Test for constructors + * + * Copyright © 2023 Luca Bacci + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + +#include +#include "../gconstructorprivate.h" + +#ifndef _WIN32 +#include +#else +#include +#endif + +#define LIB_PREFIX "lib_" +#define APP_PREFIX "app_" + +#ifdef BUILD_LIBRARY +#define PREFIX LIB_PREFIX +#else +#define PREFIX APP_PREFIX +#endif + +#if G_HAS_CONSTRUCTORS + +#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA +#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS (ctor) +#endif + +G_DEFINE_CONSTRUCTOR (ctor) + +static void +ctor (void) +{ + const char *string = PREFIX "ctor"; + + g_assert_null (g_getenv (string)); + g_setenv (string, "1", FALSE); +} + +#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA +#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS (dtor) +#endif + +G_DEFINE_DESTRUCTOR (dtor) + +static void +dtor (void) +{ + const char *string = PREFIX "dtor"; + + g_assert_null (g_getenv (string)); + g_setenv (string, "1", FALSE); +} + +#endif /* G_HAS_CONSTRUCTORS */ + +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + +extern IMAGE_DOS_HEADER __ImageBase; +static inline HMODULE +this_module (void) +{ + return (HMODULE) &__ImageBase; +} + +G_DEFINE_TLS_CALLBACK (tls_callback) + +static void NTAPI +tls_callback (PVOID hInstance, + DWORD dwReason, + LPVOID lpvReserved) +{ + /* The HINSTANCE we get must match the address of __ImageBase */ + g_assert_true (hInstance == this_module ()); + +#ifndef BUILD_LIBRARY + /* Yes, we can call GetModuleHandle (NULL) with the loader lock */ + g_assert_true (hInstance == GetModuleHandle (NULL)); +#endif + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + { + const char *string = PREFIX "tls_cb_process_attach"; + +#ifdef BUILD_LIBRARY + /* the library is explicitly loaded */ + g_assert_null (lpvReserved); +#endif + + g_assert_null (g_getenv (string)); + g_setenv (string, "1", FALSE); + } + break; + + case DLL_THREAD_ATTACH: + break; + + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: +#ifdef BUILD_LIBRARY + { + const char *string = PREFIX "tls_cb_process_detach"; + + /* the library is explicitly unloaded */ + g_assert_null (lpvReserved); + + g_assert_null (g_getenv (string)); + g_setenv (string, "1", FALSE); + } +#endif + break; + + default: + g_assert_not_reached (); + break; + } +} + +#endif /* _WIN32 && G_HAS_TLS_CALLBACKS */ + +#ifndef BUILD_LIBRARY + +void *library; + +static void +load_library (const char *library_path) +{ +#ifndef _WIN32 + library = dlopen (library_path, RTLD_LAZY); + if (!library) + g_error ("%s (%s) failed: %s", "dlopen", library_path, dlerror ()); +#else + wchar_t *library_path_u16 = g_utf8_to_utf16 (library_path, -1, NULL, NULL, NULL); + g_assert_nonnull (library_path_u16); + + library = LoadLibraryW (library_path_u16); + if (!library) + g_error ("%s (%s) failed with error code %u", + "FreeLibrary", library_path, (unsigned int) GetLastError ()); + + g_free (library_path_u16); +#endif +} + +static void +unload_library (void) +{ +#ifndef _WIN32 + if (dlclose (library) != 0) + g_error ("%s failed: %s", "dlclose", dlerror ()); +#else + if (!FreeLibrary (library)) + g_error ("%s failed with error code %u", + "FreeLibrary", (unsigned int) GetLastError ()); +#endif +} + +static void +test_app (void) +{ +#if G_HAS_CONSTRUCTORS + g_assert_cmpstr (g_getenv (APP_PREFIX "ctor"), ==, "1"); +#endif +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + g_assert_cmpstr (g_getenv (APP_PREFIX "tls_cb_process_attach"), ==, "1"); +#endif +} + +static void +test_lib (gconstpointer data) +{ + const char *library_path = (const char*) data; + + /* Check that the environment is clean */ +#if G_HAS_CONSTRUCTORS + g_assert_null (g_getenv (LIB_PREFIX "ctor")); +#endif +#if G_HAS_DESTRUCTORS + g_assert_null (g_getenv (LIB_PREFIX "dtor")); +#endif +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + g_assert_null (g_getenv (LIB_PREFIX "tls_cb_process_attach")); + g_assert_null (g_getenv (LIB_PREFIX "tls_cb_process_detach")); +#endif + + /* Constructors */ + load_library (library_path); + +#if G_HAS_CONSTRUCTORS + g_assert_cmpstr (g_getenv (LIB_PREFIX "ctor"), ==, "1"); +#endif +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + g_assert_cmpstr (g_getenv (LIB_PREFIX "tls_cb_process_attach"), ==, "1"); +#endif + + /* Destructors */ + unload_library (); + +#if G_HAS_DESTRUCTORS + g_assert_cmpstr (g_getenv (LIB_PREFIX "dtor"), ==, "1"); +#endif +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + g_assert_cmpstr (g_getenv (LIB_PREFIX "tls_cb_process_detach"), ==, "1"); +#endif +} + +int +main (int argc, char *argv[]) +{ + + const char *libname = G_STRINGIFY (LIB_NAME); + const char *builddir; + char *path; + int ret; + + g_assert_nonnull ((builddir = g_getenv ("G_TEST_BUILDDIR"))); + + path = g_build_filename (builddir, libname, NULL); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/constructor/application", test_app); + g_test_add_data_func ("/constructor/library", path, test_lib); + + ret = g_test_run (); + + g_free (path); + return ret; +} + +#endif /* !BUILD_LIBRARY */ diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 09ecd5ab3..5a06e1f52 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -306,6 +306,20 @@ if host_machine.system() == 'windows' } endif +if host_machine.system() == 'windows' or have_dlopen_dlsym + constructor_lib = shared_library('constructor-lib', + 'constructor.c', + c_args: ['-DBUILD_LIBRARY'], + dependencies: [libglib_dep], + install : false) + glib_tests += { + 'constructor' : { + 'install': false, + 'c_args': ['-DLIB_NAME=' + fs.name(constructor_lib.full_path())] + } + } +endif + foreach test_name, extra_args : glib_tests foreach std: extra_args.get('c_standards', []) if c_standards.has_key(std) From f23a31ee261ea968b7b79eaf58435d6aa124136f Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 7 Sep 2023 14:48:13 +0200 Subject: [PATCH 08/10] Use helper shared library for the constructor test --- glib/tests/constructor-helper.c | 220 ++++++++++++++++++++++++++++++++ glib/tests/constructor.c | 114 ++++++++--------- glib/tests/meson.build | 14 +- 3 files changed, 283 insertions(+), 65 deletions(-) create mode 100644 glib/tests/constructor-helper.c diff --git a/glib/tests/constructor-helper.c b/glib/tests/constructor-helper.c new file mode 100644 index 000000000..476c361b1 --- /dev/null +++ b/glib/tests/constructor-helper.c @@ -0,0 +1,220 @@ +/* constructor-helpers.c - Helper library for the constructor test + * + * Copyright © 2023 Luca Bacci + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + */ + +/* This helper library manages a set of strings. Strings can be added, + * removed and checked for presence. This library does not call into + * libc, so can be used from module constructors in a safe manner on + * a wide range of operating systems. + * + * GLib is used only for assertions or hard errors; in such cases we + * don't really care about supported libc calls, as the test ought to + * fail anyway. + */ + +#include /* for size_t */ + +#include + +#ifndef _MSC_VER +#pragma GCC push_options +#pragma GCC optimize ("O0") +#else +#pragma optimize ("", off) +#endif + +#if defined(_WIN32) + #define MODULE_EXPORT \ + __declspec (dllexport) +#elif defined (__GNUC__) + #define MODULE_EXPORT \ + __attribute__((visibility("default"))) +#else + #define MODULE_EXPORT +#endif + +char buffer[500]; +size_t position; + +MODULE_EXPORT +void string_add (const char *string); + +MODULE_EXPORT +void string_add_exclusive (const char *string); + +MODULE_EXPORT +int string_remove (const char *string); + +MODULE_EXPORT +void string_check (const char *string); + +static size_t +strlen_ (const char *string) +{ + size_t i = 0; + + g_assert_nonnull (string); + + while (string[i] != 0) + i++; + + return i; +} + +static void +memmove_ (char *buf, + size_t from, + size_t size, + size_t to) +{ + g_assert_true (to <= from); + + for (size_t i = 0; i < size; i++) + buffer[to + i] = buffer[from + i]; +} + +static void +memcpy_ (char *dst, + const char *src, + size_t size) +{ + for (size_t i = 0; i < size; i++) + dst[i] = src[i]; +} + +static void +memset_ (char *dst, + char val, + size_t size) +{ + for (size_t i = 0; i < size; i++) + dst[i] = val; +} + +static size_t +string_find_index_ (const char *string) +{ + size_t string_len; + size_t i = 0; + + g_assert_nonnull (string); + g_assert_true ((string_len = strlen_ (string)) > 0); + + for (i = 0; (i < sizeof (buffer) - position) && (buffer[i] != 0);) + { + const char *iter = &buffer[i]; + size_t len = strlen_ (iter); + + if (len == string_len && strcmp (iter, string) == 0) + return i; + + i += len + 1; + } + + return sizeof (buffer); +} + +/**< private > + * string_add: + * + * @string: NULL-terminated string. Must not be empty + * + * Adds @string to the set + */ +MODULE_EXPORT +void +string_add (const char *string) +{ + size_t len, size; + + g_assert_nonnull (string); + g_assert_true ((len = strlen_ (string)) > 0); + + size = len + 1; + + if (size > sizeof (buffer) - position) + g_error ("Not enough space in the buffer"); + + memcpy_ (buffer + position, string, size); + + position += size; +} + +/**< private > + * string_add_exclusive: + * + * @string: NULL-terminated string. Must not be empty + * + * Adds @string to the set, asserting that it's not already present. + */ +MODULE_EXPORT +void +string_add_exclusive (const char *string) +{ + if (string_find_index_ (string) < sizeof (buffer)) + g_error ("string %s already set", string); + + string_add (string); +} + +/**< private > + * string_remove: + * + * @string: NULL-terminated string. Must not be empty + * + * Removes the first occurrence of @string from the set. + * + * Returns: 1 if the string was removed, 0 otherwise. + */ +MODULE_EXPORT +int +string_remove (const char *string) +{ + size_t index = string_find_index_ (string); + size_t len, size; + + if (index >= sizeof (buffer)) + return 0; + + g_assert_true ((len = strlen_ (string)) > 0); + size = len + 1; + + memmove_ (buffer, index + size, index, position - (index + size)); + + position -= size; + + memset_ (buffer + position, 0, size); + + return 1; +} + +/**< private > + * string_check: + * + * @string: NULL-terminated string. Must not be empty + * + * Asserts that @string is present in the set + */ +MODULE_EXPORT +void +string_check (const char *string) +{ + if (string_find_index_ (string) >= sizeof (buffer)) + g_error ("String %s not present", string); +} diff --git a/glib/tests/constructor.c b/glib/tests/constructor.c index 029f5a110..57c1a8182 100644 --- a/glib/tests/constructor.c +++ b/glib/tests/constructor.c @@ -27,15 +27,20 @@ #include #endif -#define LIB_PREFIX "lib_" -#define APP_PREFIX "app_" - -#ifdef BUILD_LIBRARY -#define PREFIX LIB_PREFIX +#if defined(_WIN32) + #define MODULE_IMPORT \ + __declspec (dllimport) #else -#define PREFIX APP_PREFIX + #define MODULE_IMPORT #endif +MODULE_IMPORT +void string_add_exclusive (const char *string); + +MODULE_IMPORT +void string_check (const char *string); + + #if G_HAS_CONSTRUCTORS #ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA @@ -47,10 +52,7 @@ G_DEFINE_CONSTRUCTOR (ctor) static void ctor (void) { - const char *string = PREFIX "ctor"; - - g_assert_null (g_getenv (string)); - g_setenv (string, "1", FALSE); + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "ctor"); } #ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA @@ -62,14 +64,12 @@ G_DEFINE_DESTRUCTOR (dtor) static void dtor (void) { - const char *string = PREFIX "dtor"; - - g_assert_null (g_getenv (string)); - g_setenv (string, "1", FALSE); + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "dtor"); } #endif /* G_HAS_CONSTRUCTORS */ + #if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) extern IMAGE_DOS_HEADER __ImageBase; @@ -82,14 +82,14 @@ this_module (void) G_DEFINE_TLS_CALLBACK (tls_callback) static void NTAPI -tls_callback (PVOID hInstance, - DWORD dwReason, - LPVOID lpvReserved) +tls_callback (PVOID hInstance, + DWORD dwReason, + LPVOID lpvReserved) { /* The HINSTANCE we get must match the address of __ImageBase */ g_assert_true (hInstance == this_module ()); -#ifndef BUILD_LIBRARY +#ifdef BUILD_TEST_EXECUTABLE /* Yes, we can call GetModuleHandle (NULL) with the loader lock */ g_assert_true (hInstance == GetModuleHandle (NULL)); #endif @@ -98,15 +98,11 @@ tls_callback (PVOID hInstance, { case DLL_PROCESS_ATTACH: { - const char *string = PREFIX "tls_cb_process_attach"; - -#ifdef BUILD_LIBRARY +#ifndef BUILD_TEST_EXECUTABLE /* the library is explicitly loaded */ g_assert_null (lpvReserved); #endif - - g_assert_null (g_getenv (string)); - g_setenv (string, "1", FALSE); + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "tlscb_process_attach"); } break; @@ -117,17 +113,13 @@ tls_callback (PVOID hInstance, break; case DLL_PROCESS_DETACH: -#ifdef BUILD_LIBRARY { - const char *string = PREFIX "tls_cb_process_detach"; - +#ifndef BUILD_TEST_EXECUTABLE /* the library is explicitly unloaded */ g_assert_null (lpvReserved); - - g_assert_null (g_getenv (string)); - g_setenv (string, "1", FALSE); - } #endif + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "tlscb_process_detach"); + } break; default: @@ -138,27 +130,31 @@ tls_callback (PVOID hInstance, #endif /* _WIN32 && G_HAS_TLS_CALLBACKS */ -#ifndef BUILD_LIBRARY +#ifdef BUILD_TEST_EXECUTABLE void *library; static void -load_library (const char *library_path) +load_library (const char *path) { #ifndef _WIN32 - library = dlopen (library_path, RTLD_LAZY); + library = dlopen (path, RTLD_NOW); if (!library) - g_error ("%s (%s) failed: %s", "dlopen", library_path, dlerror ()); + { + g_error ("%s (%s) failed: %s", "dlopen", path, dlerror ()); + } #else - wchar_t *library_path_u16 = g_utf8_to_utf16 (library_path, -1, NULL, NULL, NULL); - g_assert_nonnull (library_path_u16); + wchar_t *path_utf16 = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL); + g_assert_nonnull (path_utf16); - library = LoadLibraryW (library_path_u16); + library = LoadLibraryW (path_utf16); if (!library) - g_error ("%s (%s) failed with error code %u", - "FreeLibrary", library_path, (unsigned int) GetLastError ()); + { + g_error ("%s (%s) failed with error code %u", + "FreeLibrary", path, (unsigned int) GetLastError ()); + } - g_free (library_path_u16); + g_free (path_utf16); #endif } @@ -167,11 +163,15 @@ unload_library (void) { #ifndef _WIN32 if (dlclose (library) != 0) - g_error ("%s failed: %s", "dlclose", dlerror ()); + { + g_error ("%s failed: %s", "dlclose", dlerror ()); + } #else if (!FreeLibrary (library)) - g_error ("%s failed with error code %u", - "FreeLibrary", (unsigned int) GetLastError ()); + { + g_error ("%s failed with error code %u", + "FreeLibrary", (unsigned int) GetLastError ()); + } #endif } @@ -179,10 +179,10 @@ static void test_app (void) { #if G_HAS_CONSTRUCTORS - g_assert_cmpstr (g_getenv (APP_PREFIX "ctor"), ==, "1"); + string_check ("app_" "ctor"); #endif #if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) - g_assert_cmpstr (g_getenv (APP_PREFIX "tls_cb_process_attach"), ==, "1"); + string_check ("app_" "tlscb_process_attach"); #endif } @@ -191,36 +191,24 @@ test_lib (gconstpointer data) { const char *library_path = (const char*) data; - /* Check that the environment is clean */ -#if G_HAS_CONSTRUCTORS - g_assert_null (g_getenv (LIB_PREFIX "ctor")); -#endif -#if G_HAS_DESTRUCTORS - g_assert_null (g_getenv (LIB_PREFIX "dtor")); -#endif -#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) - g_assert_null (g_getenv (LIB_PREFIX "tls_cb_process_attach")); - g_assert_null (g_getenv (LIB_PREFIX "tls_cb_process_detach")); -#endif - /* Constructors */ load_library (library_path); #if G_HAS_CONSTRUCTORS - g_assert_cmpstr (g_getenv (LIB_PREFIX "ctor"), ==, "1"); + string_check ("lib_" "ctor"); #endif #if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) - g_assert_cmpstr (g_getenv (LIB_PREFIX "tls_cb_process_attach"), ==, "1"); + string_check ("lib_" "tlscb_process_attach"); #endif /* Destructors */ unload_library (); #if G_HAS_DESTRUCTORS - g_assert_cmpstr (g_getenv (LIB_PREFIX "dtor"), ==, "1"); + string_check ("lib_" "dtor"); #endif #if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) - g_assert_cmpstr (g_getenv (LIB_PREFIX "tls_cb_process_detach"), ==, "1"); + string_check ("lib_" "tlscb_process_detach"); #endif } @@ -248,4 +236,4 @@ main (int argc, char *argv[]) return ret; } -#endif /* !BUILD_LIBRARY */ +#endif /* BUILD_TEST_EXECUTABLE */ diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 5a06e1f52..a31ea3f02 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -307,15 +307,24 @@ if host_machine.system() == 'windows' endif if host_machine.system() == 'windows' or have_dlopen_dlsym + constructor_helper = shared_library('constructor-helper', + 'constructor-helper.c', + dependencies: [libglib_dep], + install : false) constructor_lib = shared_library('constructor-lib', 'constructor.c', - c_args: ['-DBUILD_LIBRARY'], + c_args: ['-DPREFIX=lib'], dependencies: [libglib_dep], + link_with: [constructor_helper], install : false) glib_tests += { 'constructor' : { 'install': false, - 'c_args': ['-DLIB_NAME=' + fs.name(constructor_lib.full_path())] + 'c_args': ['-DLIB_NAME=' + fs.name(constructor_lib.full_path()), + '-DBUILD_TEST_EXECUTABLE', + '-DPREFIX=app'], + 'dependencies' : libdl_dep, + 'link_with': [constructor_helper] } } endif @@ -391,6 +400,7 @@ foreach test_name, extra_args : glib_tests link_args : extra_args.get('link_args', []), override_options : extra_args.get('override_options', []), dependencies : test_deps + extra_args.get('dependencies', []), + link_with : extra_args.get('link_with', []), install_dir: installed_tests_execdir, install_tag: 'tests', install: install, From 85e21ff757d0701766f58f36244e470320bae0a7 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Tue, 26 Sep 2023 18:48:38 +0200 Subject: [PATCH 09/10] tests/constructor: Test all destructors Previously we were only testing destructors that run on dlclose, now we also test destructors running at application exit. --- glib/tests/constructor.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/glib/tests/constructor.c b/glib/tests/constructor.c index 57c1a8182..955d071ab 100644 --- a/glib/tests/constructor.c +++ b/glib/tests/constructor.c @@ -65,6 +65,10 @@ static void dtor (void) { string_add_exclusive (G_STRINGIFY (PREFIX) "_" "dtor"); + +#ifdef BUILD_TEST_EXECUTABLE + _Exit (EXIT_SUCCESS); +#endif } #endif /* G_HAS_CONSTRUCTORS */ @@ -73,6 +77,7 @@ dtor (void) #if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) extern IMAGE_DOS_HEADER __ImageBase; + static inline HMODULE this_module (void) { @@ -231,9 +236,14 @@ main (int argc, char *argv[]) g_test_add_data_func ("/constructor/library", path, test_lib); ret = g_test_run (); + g_assert_cmpint (ret, ==, 0); g_free (path); - return ret; + + /* Return EXIT_FAILURE from main. The last destructor will + * call _Exit (EXIT_SUCCESS) if everything went well. This + * is a way to test that destructors get invoked */ + return EXIT_FAILURE; } #endif /* BUILD_TEST_EXECUTABLE */ From c4c39ea52a79608443c6c4eaa4ce271ef4630802 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Wed, 27 Sep 2023 16:36:42 +0200 Subject: [PATCH 10/10] tests/constructor: Support systems where dlclose is a no-op POSIX allows dlclose() implementations that are no-ops, and in such case library destructors run at application exit rather than dlclose(). That's the case, for example, of UNIX systems with the Musl LibC. --- glib/tests/constructor-helper.c | 16 ++++++++++++++++ glib/tests/constructor.c | 19 +++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/glib/tests/constructor-helper.c b/glib/tests/constructor-helper.c index 476c361b1..4b3c107b1 100644 --- a/glib/tests/constructor-helper.c +++ b/glib/tests/constructor-helper.c @@ -61,6 +61,9 @@ void string_add_exclusive (const char *string); MODULE_EXPORT int string_remove (const char *string); +MODULE_EXPORT +int string_find (const char *string); + MODULE_EXPORT void string_check (const char *string); @@ -204,6 +207,19 @@ string_remove (const char *string) return 1; } +/**< private > + * string_find: + * + * @string: NULL-terminated string. Must not be empty + * + * Returns 1 if the string is present, 0 otherwise + */ +MODULE_EXPORT +int string_find (const char *string) +{ + return string_find_index_ (string) < sizeof (buffer); +} + /**< private > * string_check: * diff --git a/glib/tests/constructor.c b/glib/tests/constructor.c index 955d071ab..001d7542f 100644 --- a/glib/tests/constructor.c +++ b/glib/tests/constructor.c @@ -40,6 +40,8 @@ void string_add_exclusive (const char *string); MODULE_IMPORT void string_check (const char *string); +MODULE_IMPORT +int string_find (const char *string); #if G_HAS_CONSTRUCTORS @@ -66,9 +68,13 @@ dtor (void) { string_add_exclusive (G_STRINGIFY (PREFIX) "_" "dtor"); -#ifdef BUILD_TEST_EXECUTABLE - _Exit (EXIT_SUCCESS); -#endif + if (string_find ("app_dtor") && string_find ("lib_dtor")) + { + /* All destructors were invoked, this is the last. + * Call _Exit (EXIT_SUCCESS) to exit immediately + * with a success code */ + _Exit (EXIT_SUCCESS); + } } #endif /* G_HAS_CONSTRUCTORS */ @@ -210,7 +216,12 @@ test_lib (gconstpointer data) unload_library (); #if G_HAS_DESTRUCTORS - string_check ("lib_" "dtor"); + /* Destructors in dynamically-loaded libraries do not + * necessarily run on dlclose. On some systems dlclose + * is effectively a no-op (e.g with the Musl LibC) and + * destructors run at program exit */ + g_test_message ("Destructors run on module unload: %s\n", + string_find ("lib_" "dtor") ? "yes" : "no"); #endif #if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) string_check ("lib_" "tlscb_process_detach");