From f9845abe3981286e3227434eeb9ca2b9069b30fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 01:03:22 +0200 Subject: [PATCH 1/6] gmacros: Define G_CXX_STD_VERSION and check macros Sadly, in C++ there's not an universal way to get what language standard is used to compile GLib-based programs, in fact while most compilers relies on `__cplusplus`, MSVC is defining that, but it does not use it to expose such information (unless `/Zc:__cplusplus` arg is used). On the other side, MSVC reports the language standard via _MSVC_LANG [1]. This complication makes us defining some macros in a very complex way (such as glib_typeof()), because we need to perform many checks just to understand if a C++ compiler is used and what standard is expecting. To avoid this, define multiple macros that can be used to figure out what C++ standard is being used. [1] https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 --- docs/reference/glib/glib-sections.txt.in | 4 + glib/docs.c | 38 ++++++++ glib/gmacros.h | 25 +++++ glib/tests/cxx.cpp | 114 ++++++++++++++++++++++- glib/tests/macros.c | 6 ++ glib/tests/meson.build | 2 +- 6 files changed, 187 insertions(+), 2 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index face9dec6..82a34683c 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -410,6 +410,10 @@ G_HAVE_GNUC_VISIBILITY G_GNUC_INTERNAL G_GNUC_MAY_ALIAS + +G_CXX_STD_VERSION +G_CXX_STD_CHECK_VERSION + G_DEPRECATED G_DEPRECATED_FOR diff --git a/glib/docs.c b/glib/docs.c index 1ca7927fb..041d90ce3 100644 --- a/glib/docs.c +++ b/glib/docs.c @@ -2527,6 +2527,44 @@ * Since: 2.6 */ +/** + * G_CXX_STD_VERSION: + * + * The C++ standard version the code is compiling against, it's defined + * with the same value of `__cplusplus` for C++ standard compatible + * compilers, while it uses `_MSVC_LANG` in MSVC, given that the + * standard definition depends on a compilation flag in such compiler. + * + * This is granted to be undefined when not compiling with a C++ compiler. + * + * See also: %G_CXX_STD_CHECK_VERSION + * + * Since: 2.76 + */ + +/** + * G_CXX_STD_CHECK_VERSION: + * @version: The C++ version to be checked for compatibility + * + * Macro to check if the current compiler supports a specified @version + * of the C++ standard. Such value must be numeric and can be provided both + * in the short form for the well-known versions (e.g. `11`, `17`...) or in + * the complete form otherwise (e.g. `201103L`, `201703L`, `205503L`...). + * + * When a C compiler is used, the macro is defined and returns always %FALSE. + * + * This value is compared against %G_CXX_STD_VERSION. + * + * |[ + * #if G_CXX_STD_CHECK_VERSION(20) + * #endif + * ]| + * + * Returns: %TRUE if @version is supported by the compiler, %FALSE otherwise + * + * Since: 2.76 + */ + /** * G_LIKELY: * @expr: the expression diff --git a/glib/gmacros.h b/glib/gmacros.h index 36d2238fc..c892c0b44 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -64,6 +64,31 @@ #define G_GNUC_EXTENSION #endif +#if !defined (__cplusplus) + +# undef G_CXX_STD_VERSION +# define G_CXX_STD_CHECK_VERSION(version) (0) + +#else /* defined (__cplusplus) */ + +# if defined (_MSVC_LANG) +# define G_CXX_STD_VERSION (_MSVC_LANG > __cplusplus ? _MSVC_LANG : __cplusplus) +# else +# define G_CXX_STD_VERSION __cplusplus +# endif /* defined(_MSVC_LANG) */ + +# define G_CXX_STD_CHECK_VERSION(version) ( \ + ((version) >= 199711L && (version) <= G_CXX_STD_VERSION) || \ + ((version) == 98 && G_CXX_STD_VERSION >= 199711L) || \ + ((version) == 03 && G_CXX_STD_VERSION >= 199711L) || \ + ((version) == 11 && G_CXX_STD_VERSION >= 201103L) || \ + ((version) == 14 && G_CXX_STD_VERSION >= 201402L) || \ + ((version) == 17 && G_CXX_STD_VERSION >= 201703L) || \ + ((version) == 20 && G_CXX_STD_VERSION >= 202002L) || \ + 0) + +#endif /* !defined (__cplusplus) */ + /* Every compiler that we target supports inlining, but some of them may * complain about it if we don't say "__inline". If we have C99, or if * we are using C++, then we can use "inline" directly. Unfortunately diff --git a/glib/tests/cxx.cpp b/glib/tests/cxx.cpp index aa5e0cb57..a2f8e4e3f 100644 --- a/glib/tests/cxx.cpp +++ b/glib/tests/cxx.cpp @@ -19,6 +19,115 @@ #include +#if !defined (G_CXX_STD_VERSION) || !defined (G_CXX_STD_CHECK_VERSION) +#error G_CXX_STD_VERSION is not defined +#endif + +G_STATIC_ASSERT (G_CXX_STD_VERSION); + +#if G_CXX_STD_VERSION >= 199711L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (98)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (199711L)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (03)); +#endif + +#if G_CXX_STD_VERSION == 199711L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201103L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (14)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201402L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201703L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 201103L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (98)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (199711L)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (03)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (201103L)); +#endif + +#if G_CXX_STD_VERSION == 201103L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (14)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201402L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201703L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 201402L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (14)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (201402L)); +#endif + +#if G_CXX_STD_VERSION == 201402L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (201703L)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 201703L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (201703L)); +#endif + +#if G_CXX_STD_VERSION == 201703L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION >= 202002L + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (20)); + G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (202002L)); +#endif + +#if G_CXX_STD_VERSION == 202002L + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (23)); + G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (202300L)); +#endif + +#ifdef _G_EXPECTED_CXX_STANDARD +static void +test_cpp_standard (void) +{ + guint64 std_version = 0; + + if (!g_ascii_string_to_unsigned (_G_EXPECTED_CXX_STANDARD, 10, 0, G_MAXUINT64, + &std_version, NULL)) + { + g_test_skip ("Expected standard value is non-numeric: " + _G_EXPECTED_CXX_STANDARD); + return; + } + +#if !G_GNUC_CHECK_VERSION (11, 0) + if (std_version >= 20) + { + // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93821 + g_test_skip ("Expected standard version is not properly supported by compiler"); + return; + } +#endif + + g_test_message ("Checking if '" G_STRINGIFY (G_CXX_STD_VERSION) "' respects " + "the expected standard version '" _G_EXPECTED_CXX_STANDARD "'"); + g_assert_true (G_CXX_STD_CHECK_VERSION (std_version)); + + if (std_version < 10 || std_version > 90) + std_version = 97; + + if (std_version >= 90) + g_assert_cmpuint (G_CXX_STD_VERSION, >=, (std_version + 1900) * 100); + else + g_assert_cmpuint (G_CXX_STD_VERSION, >=, (std_version + 2000) * 100); +} +#endif + typedef struct { int dummy; @@ -186,12 +295,15 @@ test_steal_pointer (void) int main (int argc, char *argv[]) { -#if __cplusplus >= 201103L +#if G_CXX_STD_CHECK_VERSION (11) g_test_init (&argc, &argv, NULL); #else g_test_init (&argc, &argv, static_cast(NULL)); #endif +#ifdef _G_EXPECTED_CXX_STANDARD + g_test_add_func ("/C++/check-standard-" _G_EXPECTED_CXX_STANDARD, test_cpp_standard); +#endif g_test_add_func ("/C++/typeof", test_typeof); g_test_add_func ("/C++/atomic-pointer-compare-and-exchange", test_atomic_pointer_compare_and_exchange); g_test_add_func ("/C++/atomic-pointer-compare-and-exchange-full", test_atomic_pointer_compare_and_exchange_full); diff --git a/glib/tests/macros.c b/glib/tests/macros.c index efe632b52..38372518b 100644 --- a/glib/tests/macros.c +++ b/glib/tests/macros.c @@ -22,6 +22,12 @@ #include +#ifdef G_CXX_STD_VERSION +#error G_CXX_STD_VERSION should be undefined in C programs +#endif + +G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (98)); + /* Test that G_STATIC_ASSERT_EXPR can be used as an expression */ static void test_assert_static (void) diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 2af5871ff..3ec63508b 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -187,7 +187,7 @@ if have_cxx 'cxx-@0@'.format(std) : { 'source' : ['cxx.cpp'], 'suite' : ['cpp'], - 'cpp_args' : [arg], + 'cpp_args' : [arg, '-D_G_EXPECTED_CXX_STANDARD="@0@"'.format(std)], }, } endforeach From 372ab7a964769371e47342ea9a07779e52455e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 01:31:39 +0200 Subject: [PATCH 2/6] glib: Use G_CXX_STD_VERSION to check how to behave with C++ compilers --- glib/gatomic.h | 8 ++++---- glib/glib-typeof.h | 5 ++--- glib/gmacros.h | 51 +++++++++++++++++++++------------------------- glib/gtestutils.h | 2 +- glib/tests/cxx.cpp | 2 ++ gobject/gtype.h | 5 +++-- 6 files changed, 35 insertions(+), 38 deletions(-) diff --git a/glib/gatomic.h b/glib/gatomic.h index 8c6a0877e..148424dc3 100644 --- a/glib/gatomic.h +++ b/glib/gatomic.h @@ -170,7 +170,7 @@ G_END_DECLS (void) (0 ? *(atomic) ^ *(atomic) : 1); \ __atomic_fetch_sub ((atomic), 1, __ATOMIC_SEQ_CST) == 1; \ })) -#if defined(glib_typeof) && defined(__cplusplus) +#if defined(glib_typeof) && defined(G_CXX_STD_VERSION) /* See comments below about equivalent g_atomic_pointer_compare_and_exchange() * shenanigans for type-safety when compiling in C++ mode. */ #define g_atomic_int_compare_and_exchange(atomic, oldval, newval) \ @@ -180,7 +180,7 @@ G_END_DECLS (void) (0 ? *(atomic) ^ (newval) ^ (oldval) : 1); \ __atomic_compare_exchange_n ((atomic), &gaicae_oldval, (newval), FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? TRUE : FALSE; \ })) -#else /* if !(defined(glib_typeof) && defined(__cplusplus)) */ +#else /* if !(defined(glib_typeof) && defined(G_CXX_STD_VERSION)) */ #define g_atomic_int_compare_and_exchange(atomic, oldval, newval) \ (G_GNUC_EXTENSION ({ \ gint gaicae_oldval = (oldval); \ @@ -230,7 +230,7 @@ G_END_DECLS (guint) __atomic_fetch_xor ((atomic), (val), __ATOMIC_SEQ_CST); \ })) -#if defined(glib_typeof) && defined(__cplusplus) +#if defined(glib_typeof) && defined(G_CXX_STD_VERSION) /* This is typesafe because we check we can assign oldval to the type of * (*atomic). Unfortunately it can only be done in C++ because gcc/clang warn * when atomic is volatile and not oldval, or when atomic is gsize* and oldval @@ -248,7 +248,7 @@ G_END_DECLS (void) (0 ? (gpointer) *(atomic) : NULL); \ __atomic_compare_exchange_n ((atomic), &gapcae_oldval, (newval), FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) ? TRUE : FALSE; \ })) -#else /* if !(defined(glib_typeof) && defined(__cplusplus) */ +#else /* if !(defined(glib_typeof) && defined(G_CXX_STD_VERSION) */ #define g_atomic_pointer_compare_and_exchange(atomic, oldval, newval) \ (G_GNUC_EXTENSION ({ \ G_STATIC_ASSERT (sizeof (oldval) == sizeof (gpointer)); \ diff --git a/glib/glib-typeof.h b/glib/glib-typeof.h index 106859d7b..c3519fa47 100644 --- a/glib/glib-typeof.h +++ b/glib/glib-typeof.h @@ -34,11 +34,10 @@ * This symbol is private. */ #undef glib_typeof -#if (!defined(__cplusplus) || (!defined (_MSVC_LANG) && __cplusplus < 201103L)) && \ +#if !G_CXX_STD_CHECK_VERSION (11) && \ (G_GNUC_CHECK_VERSION(4, 8) || defined(__clang__)) #define glib_typeof(t) __typeof__ (t) -#elif defined(__cplusplus) && \ - (__cplusplus >= 201103L || (defined (_MSVC_LANG) && _MSVC_LANG > 201103L)) && \ +#elif G_CXX_STD_CHECK_VERSION (11) && \ GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_68 /* C++11 decltype() is close enough for our usage */ #include diff --git a/glib/gmacros.h b/glib/gmacros.h index c892c0b44..0ca9a70d0 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -98,7 +98,7 @@ * Otherwise, we say "__inline" to avoid the warning. */ #define G_CAN_INLINE -#ifndef __cplusplus +#ifndef G_CXX_STD_VERSION # ifdef _MSC_VER # if (_MSC_VER < 1900) # define G_INLINE_DEFINE_NEEDED @@ -844,12 +844,10 @@ #ifndef __GI_SCANNER__ /* The static assert macro really confuses the introspection parser */ #define G_PASTE_ARGS(identifier1,identifier2) identifier1 ## identifier2 #define G_PASTE(identifier1,identifier2) G_PASTE_ARGS (identifier1, identifier2) -#if !defined(__cplusplus) && defined(__STDC_VERSION__) && \ +#if !defined(G_CXX_STD_VERSION) && defined(__STDC_VERSION__) && \ (__STDC_VERSION__ >= 201112L || g_macro__has_feature(c_static_assert) || g_macro__has_extension(c_static_assert)) #define G_STATIC_ASSERT(expr) _Static_assert (expr, "Expression evaluates to false") -#elif (defined(__cplusplus) && __cplusplus >= 201103L) || \ - (defined(__cplusplus) && defined (_MSC_VER) && (_MSC_VER >= 1600)) || \ - (defined (_MSC_VER) && (_MSC_VER >= 1800)) +#elif G_CXX_STD_CHECK_VERSION (11) #define G_STATIC_ASSERT(expr) static_assert (expr, "Expression evaluates to false") #else #ifdef __COUNTER__ @@ -862,14 +860,14 @@ #endif /* !__GI_SCANNER__ */ /* Provide a string identifying the current code position */ -#if defined(__GNUC__) && (__GNUC__ < 3) && !defined(__cplusplus) +#if defined (__GNUC__) && (__GNUC__ < 3) && !defined (G_CXX_STD_VERSION) #define G_STRLOC __FILE__ ":" G_STRINGIFY (__LINE__) ":" __PRETTY_FUNCTION__ "()" #else #define G_STRLOC __FILE__ ":" G_STRINGIFY (__LINE__) #endif /* Provide a string identifying the current function, non-concatenatable */ -#if defined (__GNUC__) && defined (__cplusplus) +#if defined (__GNUC__) && defined (G_CXX_STD_VERSION) #define G_STRFUNC ((const char*) (__PRETTY_FUNCTION__)) #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define G_STRFUNC ((const char*) (__func__)) @@ -880,7 +878,7 @@ #endif /* Guard C code in headers, while including them from C++ */ -#ifdef __cplusplus +#ifdef G_CXX_STD_VERSION #define G_BEGIN_DECLS extern "C" { #define G_END_DECLS } #else @@ -894,16 +892,14 @@ * defined then the current definition is correct. */ #ifndef NULL -# ifdef __cplusplus -# if __cplusplus >= 201103L -# define NULL (nullptr) -# else -# define NULL (0L) -# endif /* __cplusplus >= 201103L */ -# else /* !__cplusplus */ -# define NULL ((void*) 0) -# endif /* !__cplusplus */ -#elif defined (__cplusplus) && __cplusplus >= 201103L +# if G_CXX_STD_CHECK_VERSION (11) +# define NULL (nullptr) +# elif defined (G_CXX_STD_VERSION) +# define NULL (0L) +# else +# define NULL ((void*) 0) +# endif /* G_CXX_STD_CHECK_VERSION (11) */ +#elif G_CXX_STD_CHECK_VERSION (11) # undef NULL # define NULL (nullptr) #endif @@ -1004,7 +1000,8 @@ * * Since: 2.60 */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && \ + !defined(G_CXX_STD_VERSION) #define G_ALIGNOF(type) _Alignof (type) \ GLIB_AVAILABLE_MACRO_IN_2_60 #else @@ -1064,7 +1061,7 @@ /* Use MSVC specific syntax. */ # define G_NORETURN __declspec (noreturn) /* Use ISO C++11 syntax when the compiler supports it. */ -#elif defined (__cplusplus) && __cplusplus >= 201103 +#elif G_CXX_STD_CHECK_VERSION (11) # define G_NORETURN [[noreturn]] /* Use ISO C11 syntax when the compiler supports it. */ #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112 @@ -1135,7 +1132,7 @@ * code which includes glib.h, even if the third party code doesn’t use the new * macro itself. */ #if g_macro__has_attribute(__always_inline__) -# if defined (__cplusplus) && __cplusplus >= 201103L +# if G_CXX_STD_CHECK_VERSION (11) /* Use ISO C++11 syntax when the compiler supports it. */ # define G_ALWAYS_INLINE [[gnu::always_inline]] # else @@ -1179,20 +1176,18 @@ * code which includes glib.h, even if the third party code doesn’t use the new * macro itself. */ #if g_macro__has_attribute(__noinline__) -# if defined (__cplusplus) && __cplusplus >= 201103L +# if G_CXX_STD_CHECK_VERSION (11) /* Use ISO C++11 syntax when the compiler supports it. */ -# define G_NO_INLINE [[gnu::noinline]] +# if defined (__GNUC__) +# define G_NO_INLINE [[gnu::noinline]] +# endif # else # define G_NO_INLINE __attribute__ ((__noinline__)) # endif #elif defined (_MSC_VER) && (1200 <= _MSC_VER) /* Use MSVC specific syntax. */ -# if defined (__cplusplus) && __cplusplus >= 201103L /* Use ISO C++11 syntax when the compiler supports it. */ -# define G_NO_INLINE [[msvc::noinline]] -# else -# define G_NO_INLINE __declspec (noinline) -# endif +# define G_NO_INLINE __declspec (noinline) #else # define G_NO_INLINE /* empty */ #endif diff --git a/glib/gtestutils.h b/glib/gtestutils.h index 4e38eb414..bc404d96d 100644 --- a/glib/gtestutils.h +++ b/glib/gtestutils.h @@ -195,7 +195,7 @@ typedef void (*GTestFixtureFunc) (gpointer fixture, } G_STMT_END /* Use nullptr in C++ to catch misuse of these macros. */ -#if defined(__cplusplus) && __cplusplus >= 201100L +#if G_CXX_STD_CHECK_VERSION (11) #define g_assert_null(expr) G_STMT_START { if G_LIKELY ((expr) == nullptr) ; else \ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ "'" #expr "' should be nullptr"); \ diff --git a/glib/tests/cxx.cpp b/glib/tests/cxx.cpp index a2f8e4e3f..aebf1da56 100644 --- a/glib/tests/cxx.cpp +++ b/glib/tests/cxx.cpp @@ -140,6 +140,8 @@ test_typeof (void) MyObject *obj2 = g_rc_box_acquire (obj); g_assert_true (obj2 == obj); + G_STATIC_ASSERT (sizeof (glib_typeof (*obj)) == sizeof (glib_typeof (*obj2))); + MyObject *obj3 = g_atomic_pointer_get (&obj2); g_assert_true (obj3 == obj); diff --git a/gobject/gtype.h b/gobject/gtype.h index dc8fa25d4..f77a1a37b 100644 --- a/gobject/gtype.h +++ b/gobject/gtype.h @@ -412,7 +412,7 @@ G_BEGIN_DECLS * A numerical value which represents the unique identifier of a registered * type. */ -#if GLIB_SIZEOF_SIZE_T != GLIB_SIZEOF_LONG || !defined __cplusplus +#if GLIB_SIZEOF_SIZE_T != GLIB_SIZEOF_LONG || !defined (G_CXX_STD_VERSION) typedef gsize GType; #else /* for historic reasons, C++ links against gulong GTypes */ typedef gulong GType; @@ -2331,7 +2331,8 @@ type_name##_get_type (void) \ /* Only use this in non-C++ on GCC >= 2.7, except for Darwin/ppc64. * See https://bugzilla.gnome.org/show_bug.cgi?id=647145 */ -#if !defined (__cplusplus) && (G_GNUC_CHECK_VERSION(2, 7)) && !(defined (__APPLE__) && defined (__ppc64__)) +#if !defined (G_CXX_STD_VERSION) && (G_GNUC_CHECK_VERSION(2, 7)) && \ + !(defined (__APPLE__) && defined (__ppc64__)) #define _G_DEFINE_BOXED_TYPE_BEGIN(TypeName, type_name, copy_func, free_func) \ static GType type_name##_get_type_once (void); \ \ From 641256ea22a5bdd4f66a57c5e68c8d89882dfc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 01:59:24 +0200 Subject: [PATCH 3/6] gmacros: Prioritize the usage of [[noreturn]] in C++11 We defined G_NO_RETURN as [[noreturn]] in the C++ case, but only after trying the __attribute__ syntax, so it was never used in GNUC compatible compilers. Give it priority instead when supporting a C++11 compiler and onwards. As per this we need to adapt the code in the places where it was not properly used (leading to compilation warnings). --- glib/gmacros.h | 8 ++++---- glib/gmessages.h | 4 ++-- glib/gtestutils.h | 2 +- glib/gutils.h | 3 +-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/glib/gmacros.h b/glib/gmacros.h index 0ca9a70d0..7355a0d0c 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -1053,16 +1053,16 @@ * evaluated when a header is included. This results in warnings in third party * code which includes glib.h, even if the third party code doesn’t use the new * macro itself. */ -#if g_macro__has_attribute(__noreturn__) +#if G_CXX_STD_CHECK_VERSION (11) + /* Use ISO C++11 syntax when the compiler supports it. */ +# define G_NORETURN [[noreturn]] +#elif g_macro__has_attribute(__noreturn__) /* For compatibility with G_NORETURN_FUNCPTR on clang, use __attribute__((__noreturn__)), not _Noreturn. */ # define G_NORETURN __attribute__ ((__noreturn__)) #elif defined (_MSC_VER) && (1200 <= _MSC_VER) /* Use MSVC specific syntax. */ # define G_NORETURN __declspec (noreturn) - /* Use ISO C++11 syntax when the compiler supports it. */ -#elif G_CXX_STD_CHECK_VERSION (11) -# define G_NORETURN [[noreturn]] /* Use ISO C11 syntax when the compiler supports it. */ #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112 # define G_NORETURN _Noreturn diff --git a/glib/gmessages.h b/glib/gmessages.h index 119b72e91..eab6d0678 100644 --- a/glib/gmessages.h +++ b/glib/gmessages.h @@ -291,8 +291,8 @@ void g_warn_message (const char *domain, int line, const char *func, const char *warnexpr) G_ANALYZER_NORETURN; -GLIB_DEPRECATED G_NORETURN +GLIB_DEPRECATED void g_assert_warning (const char *log_domain, const char *file, const int line, @@ -409,7 +409,7 @@ void g_log_structured_standard (const gchar *log_domain, format) #endif #else /* no varargs macros */ -static G_NORETURN void g_error (const gchar *format, ...) G_ANALYZER_NORETURN; +G_NORETURN static void g_error (const gchar *format, ...) G_ANALYZER_NORETURN; static void g_critical (const gchar *format, ...) G_ANALYZER_NORETURN; static inline void diff --git a/glib/gtestutils.h b/glib/gtestutils.h index bc404d96d..5db453a70 100644 --- a/glib/gtestutils.h +++ b/glib/gtestutils.h @@ -541,8 +541,8 @@ void g_assertion_message (const char *domain, int line, const char *func, const char *message) G_ANALYZER_NORETURN; -GLIB_AVAILABLE_IN_ALL G_NORETURN +GLIB_AVAILABLE_IN_ALL void g_assertion_message_expr (const char *domain, const char *file, int line, diff --git a/glib/gutils.h b/glib/gutils.h index 36e2b50f5..efc691449 100644 --- a/glib/gutils.h +++ b/glib/gutils.h @@ -426,8 +426,7 @@ g_bit_storage_impl (gulong number) # include # define g_abort() abort () #else -GLIB_AVAILABLE_IN_2_50 -G_NORETURN void g_abort (void) G_ANALYZER_NORETURN; +G_NORETURN GLIB_AVAILABLE_IN_2_50 void g_abort (void) G_ANALYZER_NORETURN; #endif #endif From 14ba6995085e47f69e72d704b0a1712efd9656b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 04:15:58 +0200 Subject: [PATCH 4/6] meson: Compile some tests with multiple C standards We need to ensure that all the expected macros and utilities are working with all the supported C standards, so just repeat the tests with all the ones the compiler supports. --- glib/tests/meson.build | 23 +++++++++++++++++++++-- meson.build | 9 +++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 3ec63508b..6bea5d24b 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -3,6 +3,7 @@ glib_tests = { 'asyncqueue' : {}, 'atomic' : { 'c_args' : cc.get_id() == 'gcc' ? ['-Wstrict-aliasing=2'] : [], + 'c_standards': c_standards.keys(), }, 'base64' : {}, 'bitlock' : {}, @@ -48,7 +49,9 @@ glib_tests = { 'keyfile' : {}, 'list' : {}, 'logging' : {}, - 'macros' : {}, + 'macros' : { + 'c_standards': c_standards.keys(), + }, 'mainloop' : {}, 'mappedfile' : {}, 'mapping' : {}, @@ -152,7 +155,9 @@ glib_tests = { 'utf8-pointer' : {}, 'utf8-validate' : {}, 'utf8-misc' : {}, - 'utils' : {}, + 'utils' : { + 'c_standards': c_standards.keys(), + }, 'unicode' : {}, 'unicode-encoding' : {}, 'unicode-normalize': {}, @@ -294,6 +299,20 @@ if host_machine.system() == 'windows' } endif +foreach test_name, extra_args : glib_tests + foreach std: extra_args.get('c_standards', []) + if c_standards.has_key(std) + glib_tests += { + '@0@-c-@1@'.format(test_name, std) : extra_args + { + 'source' : extra_args.get('source', test_name + '.c'), + 'suite' : ['cc'] + extra_args.get('suite', []), + 'c_args' : [c_standards.get(std)] + extra_args.get('c_args', []), + } + } + endif + endforeach +endforeach + test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) diff --git a/meson.build b/meson.build index 518de215b..422e30653 100644 --- a/meson.build +++ b/meson.build @@ -10,6 +10,15 @@ project('glib', 'c', ) cc = meson.get_compiler('c') +c_standards = {} + +foreach std : ['90', '99', '11', '17'] + arg = (cc.get_id() == 'msvc' ? '/std:' : '-std=') + 'c' + std + if cc.has_argument(arg) + c_standards += { std: arg } + endif +endforeach + have_cxx = add_languages('cpp', native: false, required: get_option('oss_fuzz').enabled()) if have_cxx cxx = meson.get_compiler('cpp') From 633561ada27a3bef816c628a8049e31416a431cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 04:16:56 +0200 Subject: [PATCH 5/6] macros: Add a generic way to get and check the supported C standard Try to get the value of __STDC_VERSION__ if supported, if not just fallback to the oldest standard that any compiler should handle. --- docs/reference/glib/glib-sections.txt.in | 2 + glib/docs.c | 45 +++++++++++++- glib/gmacros.h | 18 ++++++ glib/tests/cxx.cpp | 5 ++ glib/tests/macros.c | 79 ++++++++++++++++++++++++ glib/tests/meson.build | 5 +- 6 files changed, 152 insertions(+), 2 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index 82a34683c..9fbce402c 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -411,6 +411,8 @@ G_GNUC_INTERNAL G_GNUC_MAY_ALIAS +G_C_STD_VERSION +G_C_STD_CHECK_VERSION G_CXX_STD_VERSION G_CXX_STD_CHECK_VERSION diff --git a/glib/docs.c b/glib/docs.c index 041d90ce3..1d24a8487 100644 --- a/glib/docs.c +++ b/glib/docs.c @@ -2527,6 +2527,47 @@ * Since: 2.6 */ +/** + * G_C_STD_VERSION: + * + * The C standard version the code is compiling against, it's normally + * defined with the same value of `__STDC_VERSION__` for C standard + * compatible compilers, while it uses the lowest standard version + * in pure MSVC, given that in such compiler the definition depends on + * a compilation flag. + * + * This is granted to be undefined when compiling with a C++ compiler. + * + * See also: %G_C_STD_CHECK_VERSION and %G_CXX_STD_VERSION + * + * Since: 2.76 + */ + +/** + * G_C_STD_CHECK_VERSION: + * @version: The C version to be checked for compatibility + * + * Macro to check if the current compiler supports a specified @version + * of the C standard. Such value must be numeric and can be provided both + * in the short form for the well-known versions (e.g. `90`, `99`...) or in + * the complete form otherwise (e.g. `199000L`, `199901L`, `205503L`...). + * + * When a C++ compiler is used, the macro is defined and returns always %FALSE. + * + * This value is compared against %G_C_STD_VERSION. + * + * |[ + * #if G_C_STD_CHECK_VERSION(17) + * #endif + * ]| + * + * See also: %G_CXX_STD_CHECK_VERSION + * + * Returns: %TRUE if @version is supported by the compiler, %FALSE otherwise + * + * Since: 2.76 + */ + /** * G_CXX_STD_VERSION: * @@ -2537,7 +2578,7 @@ * * This is granted to be undefined when not compiling with a C++ compiler. * - * See also: %G_CXX_STD_CHECK_VERSION + * See also: %G_CXX_STD_CHECK_VERSION and %G_C_STD_VERSION * * Since: 2.76 */ @@ -2560,6 +2601,8 @@ * #endif * ]| * + * See also: %G_C_STD_CHECK_VERSION + * * Returns: %TRUE if @version is supported by the compiler, %FALSE otherwise * * Since: 2.76 diff --git a/glib/gmacros.h b/glib/gmacros.h index 7355a0d0c..f0db84938 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -69,8 +69,26 @@ # undef G_CXX_STD_VERSION # define G_CXX_STD_CHECK_VERSION(version) (0) +# if defined (__STDC_VERSION__) +# define G_C_STD_VERSION __STDC_VERSION__ +# else +# define G_C_STD_VERSION 199000L +# endif /* defined (__STDC_VERSION__) */ + +# define G_C_STD_CHECK_VERSION(version) ( \ + ((version) >= 199000L && (version) <= G_C_STD_VERSION) || \ + ((version) == 89 && G_C_STD_VERSION >= 199000L) || \ + ((version) == 90 && G_C_STD_VERSION >= 199000L) || \ + ((version) == 99 && G_C_STD_VERSION >= 199901L) || \ + ((version) == 11 && G_C_STD_VERSION >= 201112L) || \ + ((version) == 17 && G_C_STD_VERSION >= 201710L) || \ + 0) + #else /* defined (__cplusplus) */ +# undef G_C_STD_VERSION +# define G_C_STD_CHECK_VERSION(version) (0) + # if defined (_MSVC_LANG) # define G_CXX_STD_VERSION (_MSVC_LANG > __cplusplus ? _MSVC_LANG : __cplusplus) # else diff --git a/glib/tests/cxx.cpp b/glib/tests/cxx.cpp index aebf1da56..86bf21b57 100644 --- a/glib/tests/cxx.cpp +++ b/glib/tests/cxx.cpp @@ -23,7 +23,12 @@ #error G_CXX_STD_VERSION is not defined #endif +#ifdef G_C_STD_VERSION +#error G_C_STD_VERSION should be undefined in C programs +#endif + G_STATIC_ASSERT (G_CXX_STD_VERSION); +G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (99)); #if G_CXX_STD_VERSION >= 199711L G_STATIC_ASSERT (G_CXX_STD_CHECK_VERSION (98)); diff --git a/glib/tests/macros.c b/glib/tests/macros.c index 38372518b..89d74d3a5 100644 --- a/glib/tests/macros.c +++ b/glib/tests/macros.c @@ -27,6 +27,81 @@ #endif G_STATIC_ASSERT (!G_CXX_STD_CHECK_VERSION (98)); +G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (89)); +G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (90)); + +#if G_C_STD_VERSION >= 199000L + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (89)); + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (90)); + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (199000L)); +#endif + +#if G_C_STD_VERSION == 198900L + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (99)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (199901L)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (201112L)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (201710L)); +#endif + +#if G_C_STD_VERSION >= 199901L + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (99)); + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (199901L)); +#endif + +#if G_C_STD_VERSION == 199901L + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (201112L)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (201710L)); +#endif + +#if G_C_STD_VERSION >= 201112L + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (11)); + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (201112L)); +#endif + +#if G_C_STD_VERSION == 201112L + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (201710L)); +#endif + +#if G_C_STD_VERSION >= 201710L + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (17)); + G_STATIC_ASSERT (G_C_STD_CHECK_VERSION (201710L)); +#endif + +#if G_C_STD_VERSION == 201710L + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (23)); + G_STATIC_ASSERT (!G_C_STD_CHECK_VERSION (202300L)); +#endif + +#ifdef _G_EXPECTED_C_STANDARD +static void +test_c_standard (void) +{ + guint64 std_version = 0; + + if (!g_ascii_string_to_unsigned (_G_EXPECTED_C_STANDARD, 10, 0, G_MAXUINT64, + &std_version, NULL)) + { + g_test_skip ("Expected standard value is non-numeric: " + _G_EXPECTED_C_STANDARD); + return; + } + + g_assert_true (G_C_STD_CHECK_VERSION (std_version)); + + if (std_version > 80 && std_version < 99) + std_version = 90; + + if (std_version >= 90) + g_assert_cmpuint (G_C_STD_VERSION, >=, (std_version + 1900) * 100); + else + g_assert_cmpuint (G_C_STD_VERSION, >=, (std_version + 2000) * 100); +} +#endif /* Test that G_STATIC_ASSERT_EXPR can be used as an expression */ static void @@ -71,6 +146,10 @@ main (int argc, { g_test_init (&argc, &argv, NULL); +#ifdef _G_EXPECTED_C_STANDARD + g_test_add_func ("/C/standard-" _G_EXPECTED_C_STANDARD, test_c_standard); +#endif + g_test_add_func ("/alignof/fallback", test_alignof_fallback); g_test_add_func ("/assert/static", test_assert_static); g_test_add_func ("/struct/sizeof_member", test_struct_sizeof_member); diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 6bea5d24b..a01649b77 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -306,7 +306,10 @@ foreach test_name, extra_args : glib_tests '@0@-c-@1@'.format(test_name, std) : extra_args + { 'source' : extra_args.get('source', test_name + '.c'), 'suite' : ['cc'] + extra_args.get('suite', []), - 'c_args' : [c_standards.get(std)] + extra_args.get('c_args', []), + 'c_args' : [ + c_standards.get(std), + '-D_G_EXPECTED_C_STANDARD="@0@"'.format(std) + ] + extra_args.get('c_args', []), } } endif From ff49707501654685ac5eb59e137ab0d84acde246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 14 Sep 2022 04:44:46 +0200 Subject: [PATCH 6/6] gmacros: Use G_C_STD_CHECK_VERSION to define C-std dependent items --- glib/gmacros.h | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/glib/gmacros.h b/glib/gmacros.h index f0db84938..9f69428f3 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -109,19 +109,19 @@ /* Every compiler that we target supports inlining, but some of them may * complain about it if we don't say "__inline". If we have C99, or if - * we are using C++, then we can use "inline" directly. Unfortunately - * Visual Studio does not support __STDC_VERSION__, so we need to check - * whether we are on Visual Studio 2013 or earlier to see that we need to - * say "__inline" in C mode. + * we are using C++, then we can use "inline" directly. * Otherwise, we say "__inline" to avoid the warning. + * Unfortunately Visual Studio does not define __STDC_VERSION__ (if not + * using /std:cXX) so we need to check whether we are on Visual Studio 2013 + * or earlier to see whether we need to say "__inline" in C mode. */ #define G_CAN_INLINE -#ifndef G_CXX_STD_VERSION +#ifdef G_C_STD_VERSION # ifdef _MSC_VER # if (_MSC_VER < 1900) # define G_INLINE_DEFINE_NEEDED # endif -# elif !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199900) +# elif !G_C_STD_CHECK_VERSION (99) # define G_INLINE_DEFINE_NEEDED # endif #endif @@ -862,8 +862,8 @@ #ifndef __GI_SCANNER__ /* The static assert macro really confuses the introspection parser */ #define G_PASTE_ARGS(identifier1,identifier2) identifier1 ## identifier2 #define G_PASTE(identifier1,identifier2) G_PASTE_ARGS (identifier1, identifier2) -#if !defined(G_CXX_STD_VERSION) && defined(__STDC_VERSION__) && \ - (__STDC_VERSION__ >= 201112L || g_macro__has_feature(c_static_assert) || g_macro__has_extension(c_static_assert)) +#if (G_C_STD_CHECK_VERSION (11) || \ + g_macro__has_feature(c_static_assert) || g_macro__has_extension(c_static_assert)) #define G_STATIC_ASSERT(expr) _Static_assert (expr, "Expression evaluates to false") #elif G_CXX_STD_CHECK_VERSION (11) #define G_STATIC_ASSERT(expr) static_assert (expr, "Expression evaluates to false") @@ -873,7 +873,7 @@ #else #define G_STATIC_ASSERT(expr) typedef char G_PASTE (_GStaticAssertCompileTimeAssertion_, __LINE__)[(expr) ? 1 : -1] G_GNUC_UNUSED #endif -#endif /* __STDC_VERSION__ */ +#endif /* G_C_STD_CHECK_VERSION (11) */ #define G_STATIC_ASSERT_EXPR(expr) ((void) sizeof (char[(expr) ? 1 : -1])) #endif /* !__GI_SCANNER__ */ @@ -887,7 +887,7 @@ /* Provide a string identifying the current function, non-concatenatable */ #if defined (__GNUC__) && defined (G_CXX_STD_VERSION) #define G_STRFUNC ((const char*) (__PRETTY_FUNCTION__)) -#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#elif G_C_STD_CHECK_VERSION (99) #define G_STRFUNC ((const char*) (__func__)) #elif defined (__GNUC__) || (defined(_MSC_VER) && (_MSC_VER > 1300)) #define G_STRFUNC ((const char*) (__FUNCTION__)) @@ -1018,8 +1018,7 @@ * * Since: 2.60 */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && \ - !defined(G_CXX_STD_VERSION) +#if G_C_STD_CHECK_VERSION (11) #define G_ALIGNOF(type) _Alignof (type) \ GLIB_AVAILABLE_MACRO_IN_2_60 #else @@ -1082,7 +1081,7 @@ /* Use MSVC specific syntax. */ # define G_NORETURN __declspec (noreturn) /* Use ISO C11 syntax when the compiler supports it. */ -#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112 +#elif G_C_STD_CHECK_VERSION (11) # define G_NORETURN _Noreturn #else # define G_NORETURN /* empty */