Created attachment 54462 [details] foo.i GCC 13 (13.0.1 20230212) seems to accept [[noreturn]] as an attribute even when not in C2x mode. This leads to a build failure with sudo-1.9.13 (https://github.com/sudo-project/sudo/issues/239) since https://github.com/sudo-project/sudo/commit/e707ffe58b3ccfe5c72f54c38eac1d7069d5021e. Clang 16.0.0_rc2 does not accept it unless passing -std=c2x. Is this intentional or not? Thanks. foo.c: ``` #include <stdlib.h> [[noreturn]] void foo(void) { abort(); } int main() { foo(); } ``` ``` $ gcc-13 --version gcc-13 (Gentoo Hardened 13.0.1_pre20230212 p8) 13.0.1 20230212 (experimental) Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ gcc-13 -std=c2x -O2 /tmp/foo.c # fine $ gcc-13 -std=c99 -O2 /tmp/foo.c # fine $ gcc-13 -O2 /tmp/foo.c -o /tmp/foo # fine $ clang-16 --version clang version 16.0.0 # actually 16.0.0_rc2 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/lib/llvm/16/bin Configuration file: /etc/clang/clang.cfg $ clang-16 -O2 /tmp/foo.c -o /tmp/foo /tmp/foo.c:3:2: error: expected expression [[noreturn]] void foo(void) { ^ /tmp/foo.c:3:14: error: expected identifier or '(' [[noreturn]] void foo(void) { ^ /tmp/foo.c:8:2: error: call to undeclared function 'foo'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] foo(); ^ 3 errors generated. $ clang-16 -std=c2x -O2 /tmp/foo.c -o /tmp/foo # fine ```
I asked on #gcc and this is intentional, it seems.
Sorry, for completeness, I meant to include: ``` $ gcc-12 -O2 /tmp/foo.c # ditto with -std=c89 /tmp/foo.c:3:1: warning: 'noreturn' attribute ignored [-Wattributes] 3 | [[noreturn]] void foo(void) { | ^ ``
*** This bug has been marked as a duplicate of bug 101682 ***
Actually in this case it is slightly different. Let me reopen it for a second.
Simple testcase: __attribute__((noreturn)) [[noreturn]] void foo(void) { while(true); } The C++ front-end accepts this but not the C front-end.
The logic is that GNU attributes are declaration specifiers (and can mix anywhere with other declaration specifiers), but standard attributes aren't declaration specifiers; rather, they come in specified positions relative to declaration specifiers (the semantics before and after the declaration specifiers are different), and in the middle isn't such a position.
(In reply to joseph@codesourcery.com from comment #6) > The logic is that GNU attributes are declaration specifiers (and can mix > anywhere with other declaration specifiers), but standard attributes > aren't declaration specifiers; rather, they come in specified positions > relative to declaration specifiers (the semantics before and after the > declaration specifiers are different), and in the middle isn't such a > position. How does that square with: ``` struct __attribute__((packed)) S { ... }; void func(int *ip) __attribute__((nonnull(1))); ``` where the GNU attribute is not written where a declaration specifier is allowed? FWIW, the Clang rationale for our behavior is that users don't really distinguish between spelling an attribute with `[[]]` or spelling it with `__attribute__` -- it's an attribute either way. We couldn't find a reason why it made sense to force users to determine arbitrary parse ordering rules for conceptually "identical" constructs. While not compatible with GCC's approach, a correct usage in GCC is expected to also be a correct usage in Clang.
On Thu, 16 Feb 2023, aaron at aaronballman dot com via Gcc-bugs wrote: > > The logic is that GNU attributes are declaration specifiers (and can mix > > anywhere with other declaration specifiers), but standard attributes > > aren't declaration specifiers; rather, they come in specified positions > > relative to declaration specifiers (the semantics before and after the > > declaration specifiers are different), and in the middle isn't such a > > position. > > How does that square with: > ``` > struct __attribute__((packed)) S { ... }; > void func(int *ip) __attribute__((nonnull(1))); > ``` > where the GNU attribute is not written where a declaration specifier is > allowed? GNU attributes are declaration specifiers *in the previous examples given here*, not necessarily in all other cases. The position in relation to other declaration specifiers does not matter in those examples. Whereas a standard attribute at the start of declaration specifiers appertains to the entity declared, while a standard attribute at the end of declaration specifiers appertains to the type in those declaration specifiers. That is [[noreturn]] void f(); declares a non-returning function f, but void [[noreturn]] f(); applies the attribute (invalidly) to the type void, not to the function f. While __attribute__((noreturn)) means exactly the same thing in both locations - it appertains to the function (and you could also have it in the middle of other declaration specifiers, with the same meaning). So the two kinds of attributes are not interchangable, and the semantics for arbitrary mixtures would not be clear. It might work to have arbitrary mixtures in the struct context. But in the void func(int *ip) __attribute__((nonnull(1))); context you again have attributes appertaining to different things: a GNU attribute in that position is in a particular position *in a declaration* (after any asm ("identifier"), before an initializer), and appertains to the entity declared, whereas a standard attribute in such a position is part of the declarator (immediately following a function-declarator or array-declarator) and appertains to the function type - although they look superficially like the same case in simple examples such as this one, they aren't at all. And so again it would be unclear what attributes in arbitrary mixtures should appertain to. (There is then logic in GCC to handle __attribute__ that, according to the syntax, should appertain to a particular entity, so that it's instead applied to some other related entity; for example, moving an attribute from a declaration to its type. This is deliberately *not* done for [[]] attribute syntax; those attributes are expected to be written in a correct location for the entity they appertain to.)
> GNU attributes are declaration specifiers *in the previous examples given > here*, not necessarily in all other cases. Thanks for clarifying! > (There is then logic in GCC to handle __attribute__ that, according to the > syntax, should appertain to a particular entity, so that it's instead > applied to some other related entity; for example, moving an attribute > from a declaration to its type. This is deliberately *not* done for [[]] > attribute syntax; those attributes are expected to be written in a correct > location for the entity they appertain to.) This touches on why I came to the decision I did in Clang. What `__attribute__` will apply to is sometimes inscrutable and users are (perhaps) used to it sliding around to whatever works. As you point out, `[[]]` doesn't have the same behavior; it has strict appertainment. Because `__attribute__` doesn't have strict appertainment, it did not seem like an issue for it to continue to shift around to whatever makes sense. Thus `[[]]` will apply to what the standard says it applies to, and `__attribute__` applies to whatever it should apply to based on the attribute names in the specifier, but users don't have to know whether they need to write `[[]] __attribute__(())` vs `__attribute__(()) [[]]`. (Clang also supports `__declspec`, so there are more combinations to worry about sometimes.) It really boils down to whether `__attribute__` is fundamentally a different "thing" than `[[]]` and I couldn't convince myself they were different. The result is, when the grammar allows consecutive attribute syntaxes, we parse all allowed syntaxes in a loop so users can write them in an arbitrary order.
One other reason for the Clang behavior that may be worth mentioning is that this helps users who wish to migrate away from `__attribute__` and towards `[[]]`. Many (most?) uses of attributes end up behind a macro, so the user may not even be aware which syntax is being used. Consider this contrived example: ``` // LibraryHeader.h #if SOMETHING #define FOO_ATTR __attribute__((foo)) #define BAR_ATTR __attribute__((bar)) #define BAZ_ATTR [[lib::baz]] #elif SOMETHING_ELSE ... #else #define FOO_ATTR #define BAR_ATTR #define BAZ_ATTR #endif // UserCode.c FOO_ATTR BAR_ATTR void func(void) { ... } ``` The user reading UserCode.c has no idea what attribute syntax is being used, nor do they probably care all that much. Under a strict parsing model, trying to add `BAZ_ATTR` to the declaration of `func()` requires the user to be very aware of exactly what each macro expands to, otherwise they might get the order wrong. With a relaxed parsing model, the user doesn't have to care. Additionally, the library header can migrate `BAR_ATTR` to `[[gnu::bar]]` syntax without also migrating `FOO_ATTR` at the same time with less fear of breaking downstream users due to attribute ordering, so this allows for gradual migration to a newer syntax. (It's not "no fear" because `[[]]` has strict appertainment rules, so it's possible for some attributes to break user code when migrating from `__attribute__` to `[[]]` due to differences in appertainment.)
*** Bug 113387 has been marked as a duplicate of this bug. ***
(In reply to Aaron Ballman from comment #10) > One other reason for the Clang behavior that may be worth mentioning is that > this helps users who wish to migrate away from `__attribute__` and towards > `[[]]`. Many (most?) uses of attributes end up behind a macro, so the user > may not even be aware which syntax is being used. Yes, exactly that's the situation in GNU gnulib. And it's not only a temporary problem, during some migration time. In fact, some of the gcc __attribute__s will probably never be available in [[...]] syntax. Thus our software will always continue to use __attribute__. And if [[...]] does not mix well with it, we will never use the [[...]] syntax for any attribute.
Everything available with __attribute__ is also available with [[]]; __attribute__((foo)) is [[gnu::foo]]. You *do* need to ensure that the [[gnu::foo]] is placed in the right syntactic position for the entity appertained to, because laxity in positioning only ever applies with __attribute__ syntax, not with [[]] syntax.
(In reply to Joseph S. Myers from comment #13) > Everything available with __attribute__ is also available with [[]]; > __attribute__((foo)) is [[gnu::foo]]. Indeed, according to https://gcc.gnu.org/onlinedocs/gcc-14.1.0/gcc/Attribute-Syntax.html that's a new feature of GCC 14. It is not mentioned in https://gcc.gnu.org/gcc-14/changes.html, though. Can it still be added to that page, or is it too late for that? > You *do* need to ensure that the > [[gnu::foo]] is placed in the right syntactic position for the entity > appertained to, because laxity in positioning only ever applies with > __attribute__ syntax, not with [[]] syntax. The documentation says "Refer to the relevant language standards for exact details on the placement of ‘[[]]’ attributes within your code". And the problem here is that this placement depends on the language: [[__deprecated__]] extern int a, b, c; is valid in C but [[__deprecated__]] extern "C" int a, b, c; is a syntax error in C++. Whereas extern [[__deprecated__]] int a, b, c; is a syntax error in C but extern "C" [[__deprecated__]] int a, b, c; is valid in C++. Similarly for function declarations and 'static inline' function definitions. How did the standards authors imagine that we write .h files that can be used from C and C++, without #if nor macros?
The [[]] syntax is supported in C from GCC 10 onwards (in C++ in older versions as well). What's new in GCC 14 is that you can use it with :: in pre-C23 modes (with previous versions you needed -std=gnu2x / -std=c2x, otherwise the :: wasn't parsed as it's two separate : tokens in older versions and only a single :: token in C23). If you're writing extern "C" in C++ and your code also supports being used from C, you already have some language conditionals.