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/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) diff --git a/glib/gconstructor.h b/glib/gconstructor.h index 9d2554c5f..ce424927e 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, @@ -110,28 +109,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: diff --git a/glib/gconstructorprivate.h b/glib/gconstructorprivate.h new file mode 100644 index 000000000..1c8e64ce6 --- /dev/null +++ b/glib/gconstructorprivate.h @@ -0,0 +1,69 @@ +/* + * 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 __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 + +#define G_DEFINE_TLS_CALLBACK(func) \ +__pragma (section (".CRT$XLCE", long, read)) \ + \ +static void NTAPI func (PVOID, DWORD, PVOID); \ + \ +G_BEGIN_DECLS \ +__declspec (allocate (".CRT$XLCE")) \ +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)) + +#else + +#define G_HAS_TLS_CALLBACKS 1 + +#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 */ + +#endif /* _WIN32 */ diff --git a/glib/glib-init.c b/glib/glib-init.c index 933f891da..7d4a4d5d9 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,60 @@ 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_THREAD_DETACH: +#ifdef THREADS_WIN32 + g_thread_win32_thread_detach (); +#endif + 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 */ 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/constructor-helper.c b/glib/tests/constructor-helper.c new file mode 100644 index 000000000..4b3c107b1 --- /dev/null +++ b/glib/tests/constructor-helper.c @@ -0,0 +1,236 @@ +/* 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 +int string_find (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_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: + * + * @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 new file mode 100644 index 000000000..001d7542f --- /dev/null +++ b/glib/tests/constructor.c @@ -0,0 +1,260 @@ +/* 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 + +#if defined(_WIN32) + #define MODULE_IMPORT \ + __declspec (dllimport) +#else + #define MODULE_IMPORT +#endif + +MODULE_IMPORT +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 + +#ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA +#pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS (ctor) +#endif + +G_DEFINE_CONSTRUCTOR (ctor) + +static void +ctor (void) +{ + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "ctor"); +} + +#ifdef G_DEFINE_DESTRUCTOR_NEEDS_PRAGMA +#pragma G_DEFINE_DESTRUCTOR_PRAGMA_ARGS (dtor) +#endif + +G_DEFINE_DESTRUCTOR (dtor) + +static void +dtor (void) +{ + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "dtor"); + + 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 */ + + +#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 ()); + +#ifdef BUILD_TEST_EXECUTABLE + /* Yes, we can call GetModuleHandle (NULL) with the loader lock */ + g_assert_true (hInstance == GetModuleHandle (NULL)); +#endif + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + { +#ifndef BUILD_TEST_EXECUTABLE + /* the library is explicitly loaded */ + g_assert_null (lpvReserved); +#endif + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "tlscb_process_attach"); + } + break; + + case DLL_THREAD_ATTACH: + break; + + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + { +#ifndef BUILD_TEST_EXECUTABLE + /* the library is explicitly unloaded */ + g_assert_null (lpvReserved); +#endif + string_add_exclusive (G_STRINGIFY (PREFIX) "_" "tlscb_process_detach"); + } + break; + + default: + g_assert_not_reached (); + break; + } +} + +#endif /* _WIN32 && G_HAS_TLS_CALLBACKS */ + +#ifdef BUILD_TEST_EXECUTABLE + +void *library; + +static void +load_library (const char *path) +{ +#ifndef _WIN32 + library = dlopen (path, RTLD_NOW); + if (!library) + { + g_error ("%s (%s) failed: %s", "dlopen", path, dlerror ()); + } +#else + wchar_t *path_utf16 = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL); + g_assert_nonnull (path_utf16); + + library = LoadLibraryW (path_utf16); + if (!library) + { + g_error ("%s (%s) failed with error code %u", + "FreeLibrary", path, (unsigned int) GetLastError ()); + } + + g_free (path_utf16); +#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 + string_check ("app_" "ctor"); +#endif +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + string_check ("app_" "tlscb_process_attach"); +#endif +} + +static void +test_lib (gconstpointer data) +{ + const char *library_path = (const char*) data; + + /* Constructors */ + load_library (library_path); + +#if G_HAS_CONSTRUCTORS + string_check ("lib_" "ctor"); +#endif +#if defined (_WIN32) && defined (G_HAS_TLS_CALLBACKS) + string_check ("lib_" "tlscb_process_attach"); +#endif + + /* Destructors */ + unload_library (); + +#if G_HAS_DESTRUCTORS + /* 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"); +#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_assert_cmpint (ret, ==, 0); + + g_free (path); + + /* 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 */ diff --git a/glib/tests/meson.build b/glib/tests/meson.build index f74bacd20..a9f44750d 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -311,6 +311,29 @@ 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: ['-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()), + '-DBUILD_TEST_EXECUTABLE', + '-DPREFIX=app'], + 'dependencies' : libdl_dep, + 'link_with': [constructor_helper] + } + } +endif + foreach test_name, extra_args : glib_tests foreach std: extra_args.get('c_standards', []) if c_standards.has_key(std) @@ -391,6 +414,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, 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 {