From 5348273cccf0c1c58ee74ac9e76aea6dc2a9ef39 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Sun, 17 Nov 2024 16:15:07 +0000 Subject: [PATCH] Emit C attributes via cbindgen --- cbindgen.globdefs.toml | 8 +++ include/features.h | 94 +++++++++++++++++++++++++----- src/header/assert/cbindgen.toml | 5 +- src/header/ctype/cbindgen.toml | 2 +- src/header/dirent/cbindgen.toml | 2 +- src/header/netdb/cbindgen.toml | 2 +- src/header/pthread/cbindgen.toml | 2 +- src/header/signal/cbindgen.toml | 2 +- src/header/stdio/cbindgen.toml | 2 +- src/header/stdlib/cbindgen.toml | 5 +- src/header/sys_time/cbindgen.toml | 2 +- src/header/sys_types/cbindgen.toml | 1 + src/header/termios/cbindgen.toml | 2 +- src/header/time/cbindgen.toml | 2 +- src/header/unistd/cbindgen.toml | 2 +- src/header/utime/cbindgen.toml | 2 +- src/header/wchar/cbindgen.toml | 2 +- tests/Makefile | 1 + tests/ctype.c | 3 + tests/expected/features.stderr | 0 tests/expected/features.stdout | 0 tests/features.c | 39 +++++++++++++ tests/futimens.c | 3 + tests/mkfifo.c | 4 ++ tests/mknod.c | 3 + tests/mknodat.c | 3 + tests/run_tests.sh | 3 +- tests/stdlib/alloc.c | 11 ++++ tests/stdlib/mktemp.c | 3 + 29 files changed, 175 insertions(+), 35 deletions(-) create mode 100644 tests/expected/features.stderr create mode 100644 tests/expected/features.stdout create mode 100644 tests/features.c diff --git a/cbindgen.globdefs.toml b/cbindgen.globdefs.toml index 3b7cd04812..0124c50d27 100644 --- a/cbindgen.globdefs.toml +++ b/cbindgen.globdefs.toml @@ -14,3 +14,11 @@ # XXX: silences a warning "feature = no_std" = "__relibc__" + +# Ensure attributes are passed down from Rust +# must be included where attributes are used in relibc +[fn] +must_use = "__nodiscard" +deprecated = "__deprecated" +deprecated_with_note = "__deprecatedNote({})" +no_return = "__noreturn" diff --git a/include/features.h b/include/features.h index 7fbdd2dcdf..592fa7e220 100644 --- a/include/features.h +++ b/include/features.h @@ -3,26 +3,92 @@ * Copyright (c) 2020 Rich Felker musl-libc */ -#ifndef _FEATURES_H -#define _FEATURES_H +#ifndef _FEATURES_H__RELIBC +#define _FEATURES_H__RELIBC -#if __STDC_VERSION__ >= 199901L -#define __restrict restrict -#elif !defined(__GNUC__) -#define __restrict +// Version metadata for feature gating +// This is useful for divergent implementation specific behavior +// glibc, ulibc, and likely others define a similar macro +// musl does not define an equivalent macro +#define __RELIBC__ 1 +#define __RELIBC__MAJOR 0 +#define __RELIBC__MINOR 2 + +/* + * Sources: + * https://en.cppreference.com/w/c/language/attributes + * https://clang.llvm.org/docs/LanguageExtensions.html + * https://gcc.gnu.org/onlinedocs/cpp/_005f_005fhas_005fc_005fattribute.html + * https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html + */ + +// Clang doesn't define __has_cpp_attribute if compiling C code +#if !defined(__has_cpp_attribute) + #define __has_cpp_attribute(x) 0 #endif -#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) -#define __inline inline -#elif !defined(__GNUC__) -#define __inline +// Clang doesn't define __has_c_attribute if compiling C++ code +#if !defined(__has_c_attribute) + #define __has_c_attribute(x) 0 #endif -#if __STDC_VERSION__ >= 201112L -#elif defined(__GNUC__) -#define _Noreturn __attribute__((__noreturn__)) +// Check if C23+ attributes are available +#if defined(__cplusplus) +// HACK: GCC backports C++ attributes to C++98 but doesn't accept attributes +// placed before the function like cbindgen emits. +// Let's just disable attributes for C++98 by checking if a random C++11 +// feature is available. +#define __HAS_ATTRIBUTE(x) __cpp_variable_templates &&__has_cpp_attribute(x) #else -#define _Noreturn +#define __HAS_ATTRIBUTE(x) \ + (__has_c_attribute(x) || __STDC_VERSION__ >= 202311L || \ + __has_cpp_attribute(x)) +#endif + +// TODO: Not emitted with cbindgen +#if __STDC_VERSION__ >= 199901L + #define __restrict restrict +#elif !defined(__GNUC__) + #define __restrict +#endif + +// TODO: Not emitted with cbindgen +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) + #define __inline inline +#elif !defined(__GNUC__) + #define __inline +#endif + +// Analogous to Rust's Never type +#if __HAS_ATTRIBUTE(noreturn) + #define __noreturn [[noreturn]] +// #elif __STDC_VERSION__ >= 201112L +// FIXME: cbindgen incorrectly places _Noreturn +// #define __noreturn _Noreturn +#elif defined(__GNUC__) + #define __noreturn __attribute__((__noreturn__)) +#else + #define __noreturn +#endif + +// Analogous to Rust's #[must_use] +// C23 only +#if __HAS_ATTRIBUTE(nodiscard) + #define __nodiscard [[nodiscard]] + #define __nodiscardNote(x) [[nodiscard(x)]] +#else + #define __nodiscard + #define __nodiscardNote(x) +#endif + +// Analogous to Rust's #[deprecated] +// C23 only +#if __HAS_ATTRIBUTE(deprecated) + #define __deprecated [[deprecated]] + #define __deprecatedNote(x) [[deprecated(x)]] +#else + #define __deprecated + #define __deprecatedNote(x) #endif #endif diff --git a/src/header/assert/cbindgen.toml b/src/header/assert/cbindgen.toml index 2db3f66e2c..e526f6745c 100644 --- a/src/header/assert/cbindgen.toml +++ b/src/header/assert/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = [] +sys_includes = ["features.h"] include_guard = "_RELIBC_ASSERT_H" trailer = "#include " language = "C" @@ -8,6 +8,3 @@ cpp_compat = true [enum] prefix_with_name = true - -[fn] -no_return = "__attribute__((noreturn))" diff --git a/src/header/ctype/cbindgen.toml b/src/header/ctype/cbindgen.toml index f47026b46c..0f42238c8a 100644 --- a/src/header/ctype/cbindgen.toml +++ b/src/header/ctype/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["bits/ctype.h"] +sys_includes = ["bits/ctype.h", "features.h"] include_guard = "_RELIBC_CTYPE_H" language = "C" style = "Tag" diff --git a/src/header/dirent/cbindgen.toml b/src/header/dirent/cbindgen.toml index 5ba9d76a14..7c665c9c11 100644 --- a/src/header/dirent/cbindgen.toml +++ b/src/header/dirent/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/types.h"] +sys_includes = ["sys/types.h", "features.h"] include_guard = "_RELIBC_DIRENT_H" language = "C" style = "Both" diff --git a/src/header/netdb/cbindgen.toml b/src/header/netdb/cbindgen.toml index b4332479d2..ed7f34af4a 100644 --- a/src/header/netdb/cbindgen.toml +++ b/src/header/netdb/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/socket.h", "netinet/in.h"] +sys_includes = ["sys/socket.h", "netinet/in.h", "features.h"] include_guard = "_RELIBC_NETDB_H" trailer = "#include " language = "C" diff --git a/src/header/pthread/cbindgen.toml b/src/header/pthread/cbindgen.toml index 3018f61605..90ed503f46 100644 --- a/src/header/pthread/cbindgen.toml +++ b/src/header/pthread/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sched.h", "time.h", "bits/pthread.h"] +sys_includes = ["sched.h", "time.h", "bits/pthread.h", "features.h"] include_guard = "_RELIBC_PTHREAD_H" language = "C" style = "tag" diff --git a/src/header/signal/cbindgen.toml b/src/header/signal/cbindgen.toml index f29e6aa396..f1b6b51709 100644 --- a/src/header/signal/cbindgen.toml +++ b/src/header/signal/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["bits/signal.h", "stdint.h", "sys/types.h", "time.h", "bits/pthread.h"] +sys_includes = ["bits/signal.h", "stdint.h", "sys/types.h", "time.h", "bits/pthread.h", "features.h"] include_guard = "_RELIBC_SIGNAL_H" trailer = "#include " language = "C" diff --git a/src/header/stdio/cbindgen.toml b/src/header/stdio/cbindgen.toml index 633b449850..2be650affa 100644 --- a/src/header/stdio/cbindgen.toml +++ b/src/header/stdio/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h"] +sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h", "features.h"] include_guard = "_RELIBC_STDIO_H" trailer = "#include " language = "C" diff --git a/src/header/stdlib/cbindgen.toml b/src/header/stdlib/cbindgen.toml index 6de6a6fa4b..6e8e15ec25 100644 --- a/src/header/stdlib/cbindgen.toml +++ b/src/header/stdlib/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["stddef.h", "alloca.h", "wchar.h"] +sys_includes = ["stddef.h", "alloca.h", "wchar.h", "features.h"] include_guard = "_RELIBC_STDLIB_H" trailer = "#include " language = "C" @@ -8,6 +8,3 @@ cpp_compat = true [enum] prefix_with_name = true - -[fn] -no_return = "__attribute__((noreturn))" diff --git a/src/header/sys_time/cbindgen.toml b/src/header/sys_time/cbindgen.toml index 535c6a246b..c7894fcfcc 100644 --- a/src/header/sys_time/cbindgen.toml +++ b/src/header/sys_time/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/types.h"] +sys_includes = ["sys/types.h", "features.h"] include_guard = "_SYS_TIME_H" language = "C" trailer = "#include " diff --git a/src/header/sys_types/cbindgen.toml b/src/header/sys_types/cbindgen.toml index e6f3294414..e33c3366ad 100644 --- a/src/header/sys_types/cbindgen.toml +++ b/src/header/sys_types/cbindgen.toml @@ -7,6 +7,7 @@ sys_includes = [ "stddef.h", "sys/select.h", "bits/pthread.h", + "features.h", ] include_guard = "_SYS_TYPES_H" diff --git a/src/header/termios/cbindgen.toml b/src/header/termios/cbindgen.toml index c5b90ae823..24444552f4 100644 --- a/src/header/termios/cbindgen.toml +++ b/src/header/termios/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["stdint.h"] +sys_includes = ["stdint.h", "features.h"] include_guard = "_RELIBC_TERMIOS_H" language = "C" style = "Tag" diff --git a/src/header/time/cbindgen.toml b/src/header/time/cbindgen.toml index 3a77d78d29..0ca04e77a9 100644 --- a/src/header/time/cbindgen.toml +++ b/src/header/time/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/types.h", "stdint.h", "stddef.h"] +sys_includes = ["sys/types.h", "stdint.h", "stddef.h", "features.h"] include_guard = "_RELIBC_TIME_H" language = "C" style = "Tag" diff --git a/src/header/unistd/cbindgen.toml b/src/header/unistd/cbindgen.toml index 5175b76441..c750d99c59 100644 --- a/src/header/unistd/cbindgen.toml +++ b/src/header/unistd/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["stddef.h", "stdint.h", "sys/types.h"] +sys_includes = ["stddef.h", "stdint.h", "sys/types.h", "features.h"] include_guard = "_RELIBC_UNISTD_H" trailer = """ #include diff --git a/src/header/utime/cbindgen.toml b/src/header/utime/cbindgen.toml index f09f3754ea..722d677d06 100644 --- a/src/header/utime/cbindgen.toml +++ b/src/header/utime/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/types.h"] +sys_includes = ["sys/types.h", "features.h"] include_guard = "_RELIBC_UTIME_H" language = "C" style = "Tag" diff --git a/src/header/wchar/cbindgen.toml b/src/header/wchar/cbindgen.toml index 08dbb38988..f4d1e4893f 100644 --- a/src/header/wchar/cbindgen.toml +++ b/src/header/wchar/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["stddef.h", "stdint.h", "stdio.h", "time.h", "bits/wchar.h"] +sys_includes = ["stddef.h", "stdint.h", "stdio.h", "time.h", "bits/wchar.h", "features.h"] include_guard = "_RELIBC_WCHAR_H" language = "C" style = "Type" diff --git a/tests/Makefile b/tests/Makefile index dcbcdf7614..766fe5ebd1 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -19,6 +19,7 @@ EXPECT_NAMES=\ error \ fcntl/create \ fcntl/fcntl \ + features \ fnmatch \ futimens \ libgen \ diff --git a/tests/ctype.c b/tests/ctype.c index ffef3402f1..0f6aa1d1bc 100644 --- a/tests/ctype.c +++ b/tests/ctype.c @@ -301,6 +301,8 @@ size_t num_test_cases = sizeof(test_cases) / sizeof(struct test_case); int main(void) { int retval = EXIT_SUCCESS; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" for(size_t i = 0; i < num_test_cases; ++i) { struct test_case tc = test_cases[i]; CHECK_TEST(tc, isalnum, retval); @@ -320,6 +322,7 @@ int main(void) { CHECK_TEST(tc, tolower, retval); CHECK_TEST(tc, toupper, retval); } + #pragma GCC diagnostic pop if (retval == EXIT_SUCCESS) { printf("Success: %d\n", retval); diff --git a/tests/expected/features.stderr b/tests/expected/features.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/features.stdout b/tests/expected/features.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/features.c b/tests/features.c new file mode 100644 index 0000000000..1a7bcc3db6 --- /dev/null +++ b/tests/features.c @@ -0,0 +1,39 @@ +// These tests are primarily to ensure the macros compile without +// causing any funny business. + +#include +#include +#include +#include + +__deprecated +static void legacy(void) {} + +__deprecatedNote("Sometimes deletes user's home (oops); use foobar") +static void legacy_notes(void) {} + +__nodiscard +static uint8_t the_answer(void) { + return 42; +} + +// GCC bug +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" +__noreturn +static void foobar(void) { + exit(0); +} +#pragma GCC diagnostic pop + +int main(void) { + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + legacy(); + legacy_notes(); + #pragma GCC diagnostic pop + const int answer = the_answer(); + char buf[40] = {0}; + sprintf(buf, "Hey, -Werror, I'm using answer: %d\n", answer); + foobar(); +} diff --git a/tests/futimens.c b/tests/futimens.c index feedaf0e3c..20b20ab15b 100644 --- a/tests/futimens.c +++ b/tests/futimens.c @@ -42,7 +42,10 @@ int main(void) { exit(1); } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" strncat(path, mktemp(temp), sizeof(temp)); + #pragma GCC diagnostic pop strncat(path, file, sizeof(file)); if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); diff --git a/tests/mkfifo.c b/tests/mkfifo.c index c242f05771..ad4dec2a9c 100644 --- a/tests/mkfifo.c +++ b/tests/mkfifo.c @@ -18,7 +18,11 @@ int main(void) { exit(1); } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" path = strncat(path, mktemp(temp), sizeof(temp)); + #pragma GCC diagnostic pop path = strncat(path, file, sizeof(file)); if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); diff --git a/tests/mknod.c b/tests/mknod.c index 80658866ec..58e279f688 100644 --- a/tests/mknod.c +++ b/tests/mknod.c @@ -18,10 +18,13 @@ int main(void) { exit(1); } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" if(!mktemp(temp)) { fprintf(stderr, "Unable to create a unique dir name %s: %s\n", temp, strerror(errno)); exit(1); } + #pragma GCC diagnostic pop path = strncat(path, temp, strlen(temp)); path = strncat(path, file, strlen(file)); diff --git a/tests/mknodat.c b/tests/mknodat.c index 39c2016ece..b449a30d5b 100644 --- a/tests/mknodat.c +++ b/tests/mknodat.c @@ -19,10 +19,13 @@ int main(void) { exit(1); } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" if(!mktemp(temp)) { fprintf(stderr, "Unable to create a unique dir name %s: %s\n", temp, strerror(errno)); exit(1); } + #pragma GCC diagnostic pop if (mkdir(temp, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) { fprintf(stderr, "mkdir %s: %s\n", temp, strerror(errno)); diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 72685a7951..eb93eb58b1 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -21,6 +21,7 @@ EXPECT_NAMES=(\ error \ fcntl/create \ fcntl/fcntl \ + features \ fnmatch \ futimens \ libgen \ @@ -205,4 +206,4 @@ STATUS_NAMES=(\ EXPECT_BINS=(${EXPECT_NAMES[@]/#/.\/bins_static\/}) STATUS_BINS=(${STATUS_NAMES[@]/#/-s.\/bins_static\/}) -bins_verify/relibc-tests ${STATUS_BINS[@]} ${EXPECT_BINS[@]} \ No newline at end of file +bins_verify/relibc-tests ${STATUS_BINS[@]} ${EXPECT_BINS[@]} diff --git a/tests/stdlib/alloc.c b/tests/stdlib/alloc.c index dbddea02b8..2342de14d1 100644 --- a/tests/stdlib/alloc.c +++ b/tests/stdlib/alloc.c @@ -182,6 +182,9 @@ int main(void) { test_cannot_alloc(ptr_reallocarray_maxsize, reallocarray_errno); free(ptr_reallocarray_maxsize); + // Warning for deprecated functions is expected so silence -Werror + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" errno = 0; char * ptr_memalign_size0 = (char *)memalign(aligned_alloc_alignment, zero_size); int memalign_size0_errno = errno; @@ -219,6 +222,8 @@ int main(void) { printf("memalign (alignment 3): "); test_invalid_aligned(ptr_memalign_align3, memalign_align3_errno); free(ptr_memalign_align3); + + #pragma GCC diagnostic pop errno = 0; char * ptr_aligned_alloc_goodsize = (char *)aligned_alloc(aligned_alloc_alignment, aligned_alloc_goodsize); @@ -234,6 +239,10 @@ int main(void) { test_invalid_aligned(ptr_aligned_alloc_badsize, aligned_alloc_badsize_errno); free(ptr_aligned_alloc_badsize); + // Warning for deprecated functions is expected so silence -Werror + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + errno = 0; char * ptr_valloc_size0 = (char *)valloc(zero_size); int valloc_size0_errno = errno; @@ -289,4 +298,6 @@ int main(void) { printf("posix_memalign (SIZE_MAX): "); test_cannot_alloc(ptr_posix_memalign_maxsize, posix_memalign_maxsize_return); free(ptr_posix_memalign_maxsize); + + #pragma GCC diagnostic pop } diff --git a/tests/stdlib/mktemp.c b/tests/stdlib/mktemp.c index 7d43078e4b..577fb51fc2 100644 --- a/tests/stdlib/mktemp.c +++ b/tests/stdlib/mktemp.c @@ -7,7 +7,10 @@ int main(void) { char* string = (char*) calloc(20, sizeof(char)); strcpy(string, "tempXXXXXX"); + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" mktemp(string); + #pragma GCC diagnostic pop printf("%s\n",string); free(string); }