From f23a31ee261ea968b7b79eaf58435d6aa124136f Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Thu, 7 Sep 2023 14:48:13 +0200 Subject: [PATCH] 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,