From 1d00f8c86324c40ab2ba7933366d380e32c0a94a Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Thu, 12 Nov 2020 21:13:51 +0000 Subject: [PATCH] c: C2x __has_c_attribute C2x adds the __has_c_attribute preprocessor operator, similar to C++ __has_cpp_attribute. GCC implements __has_cpp_attribute as exactly equivalent to __has_attribute. (The documentation says they differ regarding the values returned for standard attributes, but that's actually only a matter of the particular nonzero value returned not being specified in the documentation for __has_attribute; the implementation makes no distinction between the two.) I don't think having them exactly equivalent is actually correct, either for __has_cpp_attribute or for __has_c_attribute. Specifically, I think it is only correct for __has_cpp_attribute or __has_c_attribute to return nonzero if the given attribute is supported, with the particular pp-tokens passed to __has_cpp_attribute or __has_c_attribute, with [[]] syntax, not if it's only accepted in __attribute__ or with gnu:: added in [[]]. For example, they should return nonzero for gnu::packed, but zero for plain packed, because [[gnu::packed]] is accepted but [[packed]] is ignored as not a standard attribute. This patch implements that for __has_c_attribute, leaving any changes to __has_cpp_attribute for the C++ maintainers. A new BT_HAS_STD_ATTRIBUTE is added for __has_c_attribute (which I think, based on the above, would actually be correct to use for __has_cpp_attribute as well). The code in c_common_has_attribute that deals with scopes has its C++ conditional removed; instead, whether the language is C or C++ is used only to determine the numeric values returned for standard attributes (and which standard attributes are handled there at all). A new argument is passed to c_common_has_attribute to distinguish BT_HAS_STD_ATTRIBUTE from BT_HAS_ATTRIBUTE, and that argument is used to stop attributes with no scope specified from being accepted with __has_c_attribute unless they are one of the known standard attributes and so handled specially. Although the standard specify constants ending with 'L' as the values for the standard attributes, there is no correctness issue with the lack of code in GCC to add that 'L' to the expansion: __has_c_attribute and __has_cpp_attribute are expanded in #if after other macro expansion has occurred, with no semantics being specified if they occur outside #if, so there is no way for a conforming program to inspect the exact text of the expansion of those macros, only to use the resulting pp-number in a #if expression, where long and int have the same set of values. Bootstrapped with no regressions for x86_64-pc-linux-gnu. gcc/ 2020-11-12 Joseph Myers * doc/cpp.texi (__has_attribute): Document when scopes are allowed for C. (__has_c_attribute): New. gcc/c-family/ 2020-11-12 Joseph Myers * c-lex.c (c_common_has_attribute): Take argument std_syntax. Allow scope for C. Handle standard attributes for C. Do not accept unscoped attributes if std_syntax and not handled as standard attributes. * c-common.h (c_common_has_attribute): Update prototype. gcc/testsuite/ 2020-11-12 Joseph Myers * gcc.dg/c2x-has-c-attribute-1.c, gcc.dg/c2x-has-c-attribute-2.c, gcc.dg/c2x-has-c-attribute-3.c, gcc.dg/c2x-has-c-attribute-4.c: New tests. libcpp/ 2020-11-12 Joseph Myers * include/cpplib.h (struct cpp_callbacks): Add bool argument to has_attribute. (enum cpp_builtin_type): Add BT_HAS_STD_ATTRIBUTE. * init.c (builtin_array): Add __has_c_attribute. (cpp_init_special_builtins): Handle BT_HAS_STD_ATTRIBUTE. * macro.c (_cpp_builtin_macro_text): Handle BT_HAS_STD_ATTRIBUTE. Update call to has_attribute for BT_HAS_ATTRIBUTE. * traditional.c (fun_like_macro): Handle BT_HAS_STD_ATTRIBUTE. --- gcc/c-family/c-common.h | 2 +- gcc/c-family/c-lex.c | 67 ++++++++++++-------- gcc/doc/cpp.texi | 21 +++++- gcc/testsuite/gcc.dg/c2x-has-c-attribute-1.c | 28 ++++++++ gcc/testsuite/gcc.dg/c2x-has-c-attribute-2.c | 41 ++++++++++++ gcc/testsuite/gcc.dg/c2x-has-c-attribute-3.c | 25 ++++++++ gcc/testsuite/gcc.dg/c2x-has-c-attribute-4.c | 18 ++++++ libcpp/include/cpplib.h | 3 +- libcpp/init.c | 2 + libcpp/macro.c | 6 +- libcpp/traditional.c | 1 + 11 files changed, 181 insertions(+), 33 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/c2x-has-c-attribute-1.c create mode 100644 gcc/testsuite/gcc.dg/c2x-has-c-attribute-2.c create mode 100644 gcc/testsuite/gcc.dg/c2x-has-c-attribute-3.c create mode 100644 gcc/testsuite/gcc.dg/c2x-has-c-attribute-4.c diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 94f4868915a..f47097442eb 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -1042,7 +1042,7 @@ extern bool c_cpp_diagnostic (cpp_reader *, enum cpp_diagnostic_level, enum cpp_warning_reason, rich_location *, const char *, va_list *) ATTRIBUTE_GCC_DIAG(5,0); -extern int c_common_has_attribute (cpp_reader *); +extern int c_common_has_attribute (cpp_reader *, bool); extern int c_common_has_builtin (cpp_reader *); extern bool parse_optimize_options (tree, bool); diff --git a/gcc/c-family/c-lex.c b/gcc/c-family/c-lex.c index e81e16ddc26..6cd3df7c96f 100644 --- a/gcc/c-family/c-lex.c +++ b/gcc/c-family/c-lex.c @@ -300,7 +300,7 @@ get_token_no_padding (cpp_reader *pfile) /* Callback for has_attribute. */ int -c_common_has_attribute (cpp_reader *pfile) +c_common_has_attribute (cpp_reader *pfile, bool std_syntax) { int result = 0; tree attr_name = NULL_TREE; @@ -319,35 +319,37 @@ c_common_has_attribute (cpp_reader *pfile) attr_name = get_identifier ((const char *) cpp_token_as_text (pfile, token)); attr_name = canonicalize_attr_name (attr_name); - if (c_dialect_cxx ()) + bool have_scope = false; + int idx = 0; + const cpp_token *nxt_token; + do + nxt_token = cpp_peek_token (pfile, idx++); + while (nxt_token->type == CPP_PADDING); + if (nxt_token->type == CPP_SCOPE) { - int idx = 0; - const cpp_token *nxt_token; - do - nxt_token = cpp_peek_token (pfile, idx++); - while (nxt_token->type == CPP_PADDING); - if (nxt_token->type == CPP_SCOPE) + have_scope = true; + get_token_no_padding (pfile); // Eat scope. + nxt_token = get_token_no_padding (pfile); + if (nxt_token->type == CPP_NAME) { - get_token_no_padding (pfile); // Eat scope. - nxt_token = get_token_no_padding (pfile); - if (nxt_token->type == CPP_NAME) - { - tree attr_ns = attr_name; - tree attr_id - = get_identifier ((const char *) - cpp_token_as_text (pfile, nxt_token)); - attr_name = build_tree_list (attr_ns, attr_id); - } - else - { - cpp_error (pfile, CPP_DL_ERROR, - "attribute identifier required after scope"); - attr_name = NULL_TREE; - } + tree attr_ns = attr_name; + tree attr_id + = get_identifier ((const char *) + cpp_token_as_text (pfile, nxt_token)); + attr_name = build_tree_list (attr_ns, attr_id); } else { - /* Some standard attributes need special handling. */ + cpp_error (pfile, CPP_DL_ERROR, + "attribute identifier required after scope"); + attr_name = NULL_TREE; + } + } + else + { + /* Some standard attributes need special handling. */ + if (c_dialect_cxx ()) + { if (is_attribute_p ("noreturn", attr_name)) result = 200809; else if (is_attribute_p ("deprecated", attr_name)) @@ -361,11 +363,20 @@ c_common_has_attribute (cpp_reader *pfile) result = 201803; else if (is_attribute_p ("nodiscard", attr_name)) result = 201907; - if (result) - attr_name = NULL_TREE; } + else + { + if (is_attribute_p ("deprecated", attr_name) + || is_attribute_p ("maybe_unused", attr_name) + || is_attribute_p ("fallthrough", attr_name)) + result = 201904; + else if (is_attribute_p ("nodiscard", attr_name)) + result = 202003; + } + if (result) + attr_name = NULL_TREE; } - if (attr_name) + if (attr_name && (have_scope || !std_syntax)) { init_attributes (); const struct attribute_spec *attr = lookup_attribute_spec (attr_name); diff --git a/gcc/doc/cpp.texi b/gcc/doc/cpp.texi index 33f876ab706..291e14676be 100644 --- a/gcc/doc/cpp.texi +++ b/gcc/doc/cpp.texi @@ -3159,6 +3159,7 @@ directive}: @samp{#if}, @samp{#ifdef} or @samp{#ifndef}. * Elif:: * @code{__has_attribute}:: * @code{__has_cpp_attribute}:: +* @code{__has_c_attribute}:: * @code{__has_builtin}:: * @code{__has_include}:: @end menu @@ -3432,8 +3433,9 @@ condition succeeds after the original @samp{#if} and all previous The special operator @code{__has_attribute (@var{operand})} may be used in @samp{#if} and @samp{#elif} expressions to test whether the attribute referenced by its @var{operand} is recognized by GCC. Using the operator -in other contexts is not valid. In C code, @var{operand} must be -a valid identifier. In C++ code, @var{operand} may be optionally +in other contexts is not valid. In C code, if compiling for strict +conformance to standards before C2x, @var{operand} must be +a valid identifier. Otherwise, @var{operand} may be optionally introduced by the @code{@var{attribute-scope}::} prefix. The @var{attribute-scope} prefix identifies the ``namespace'' within which the attribute is recognized. The scope of GCC attributes is @@ -3479,6 +3481,21 @@ information including the dates of the introduction of current standard attributes, see @w{@uref{https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations/, SD-6: SG10 Feature Test Recommendations}}. +@node @code{__has_c_attribute} +@subsection @code{__has_c_attribute} +@cindex @code{__has_c_attribute} + +The special operator @code{__has_c_attribute (@var{operand})} may be +used in @samp{#if} and @samp{#elif} expressions in C code to test +whether the attribute referenced by its @var{operand} is recognized by +GCC in attributes using the @samp{[[]]} syntax. GNU attributes must +be specified with the scope @samp{gnu} or @samp{__gnu__} with +@code{__has_c_attribute}. When @var{operand} designates a supported +standard attribute it evaluates to an integer constant of the form +@code{YYYYMM} indicating the year and month when the attribute was +first introduced into the C standard, or when the syntax of operands +to the attribute was extended in the C standard. + @node @code{__has_builtin} @subsection @code{__has_builtin} @cindex @code{__has_builtin} diff --git a/gcc/testsuite/gcc.dg/c2x-has-c-attribute-1.c b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-1.c new file mode 100644 index 00000000000..fe06abf99ba --- /dev/null +++ b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-1.c @@ -0,0 +1,28 @@ +/* Test __has_c_attribute. Test basic properties. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c2x -pedantic-errors" } */ + +#ifdef __has_c_attribute +/* OK. */ +#else +#error "__has_c_attribute not defined" +#endif + +#ifndef __has_c_attribute +#error "__has_c_attribute not defined" +#endif + +#if defined __has_c_attribute +/* OK. */ +#else +#error "__has_c_attribute not defined" +#endif + +#if __has_c_attribute(foo) +#error "foo attribute supported" +#endif + +#if 0 +#elif __has_c_attribute(foo) +#error "foo attribute supported" +#endif diff --git a/gcc/testsuite/gcc.dg/c2x-has-c-attribute-2.c b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-2.c new file mode 100644 index 00000000000..d6c4c6de509 --- /dev/null +++ b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-2.c @@ -0,0 +1,41 @@ +/* Test __has_c_attribute. Test supported attributes. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c2x -pedantic-errors" } */ + +#if __has_c_attribute ( nodiscard ) != 202003L +#error "bad result for nodiscard" +#endif + +#if __has_c_attribute ( __nodiscard__ ) != 202003L +#error "bad result for __nodiscard__" +#endif + +#if __has_c_attribute(maybe_unused) != 201904L +#error "bad result for maybe_unused" +#endif + +#if __has_c_attribute(__maybe_unused__) != 201904L +#error "bad result for __maybe_unused__" +#endif + +#if __has_c_attribute (deprecated) != 201904L +#error "bad result for deprecated" +#endif + +#if __has_c_attribute (__deprecated__) != 201904L +#error "bad result for __deprecated__" +#endif + +#if __has_c_attribute (fallthrough) != 201904L +#error "bad result for fallthrough" +#endif + +#if __has_c_attribute (__fallthrough__) != 201904L +#error "bad result for __fallthrough__" +#endif + +/* Macros in the attribute name are expanded. */ +#define foo deprecated +#if __has_c_attribute (foo) != 201904L +#error "bad result for foo" +#endif diff --git a/gcc/testsuite/gcc.dg/c2x-has-c-attribute-3.c b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-3.c new file mode 100644 index 00000000000..36842ed41bc --- /dev/null +++ b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-3.c @@ -0,0 +1,25 @@ +/* Test __has_c_attribute. Test GNU attributes. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c2x -pedantic-errors" } */ + +#if __has_c_attribute (gnu::packed) != 1 +#error "bad result for gnu::packed" +#endif + +#if __has_c_attribute (__gnu__::__packed__) != 1 +#error "bad result for __gnu__::__packed__" +#endif + +#if __has_c_attribute (gnu::__packed__) != 1 +#error "bad result for gnu::__packed__" +#endif + +#if __has_c_attribute (__gnu__::packed) != 1 +#error "bad result for __gnu__::packed" +#endif + +/* GNU attributes should not be reported as accepted without a scope + specified. */ +#if __has_c_attribute (packed) != 0 +#error "bad result for packed" +#endif diff --git a/gcc/testsuite/gcc.dg/c2x-has-c-attribute-4.c b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-4.c new file mode 100644 index 00000000000..acd35d2d5ac --- /dev/null +++ b/gcc/testsuite/gcc.dg/c2x-has-c-attribute-4.c @@ -0,0 +1,18 @@ +/* Test __has_c_attribute. Test syntax errors. */ +/* { dg-do preprocess } */ +/* { dg-options "-std=c2x -pedantic-errors" } */ + +#if __has_c_attribute /* { dg-error "missing '\\('" } */ +#endif + +#if __has_c_attribute 0 /* { dg-error "missing '\\('" } */ +#endif + +#if __has_c_attribute (0 /* { dg-error "requires an identifier" } */ +#endif + +#if __has_c_attribute (x /* { dg-error "missing '\\)'" } */ +#endif + +#if __has_c_attribute (x::0) /* { dg-error "required after scope" } */ +#endif diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index c4d7cc520d1..8900e77c6e5 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -672,7 +672,7 @@ struct cpp_callbacks void (*used) (cpp_reader *, location_t, cpp_hashnode *); /* Callback to identify whether an attribute exists. */ - int (*has_attribute) (cpp_reader *); + int (*has_attribute) (cpp_reader *, bool); /* Callback to determine whether a built-in function is recognized. */ int (*has_builtin) (cpp_reader *); @@ -857,6 +857,7 @@ enum cpp_builtin_type BT_TIMESTAMP, /* `__TIMESTAMP__' */ BT_COUNTER, /* `__COUNTER__' */ BT_HAS_ATTRIBUTE, /* `__has_attribute(x)' */ + BT_HAS_STD_ATTRIBUTE, /* `__has_c_attribute(x)' */ BT_HAS_BUILTIN, /* `__has_builtin(x)' */ BT_HAS_INCLUDE, /* `__has_include(x)' */ BT_HAS_INCLUDE_NEXT /* `__has_include_next(x)' */ diff --git a/libcpp/init.c b/libcpp/init.c index dcf1d4be587..1b43802c29c 100644 --- a/libcpp/init.c +++ b/libcpp/init.c @@ -407,6 +407,7 @@ static const struct builtin_macro builtin_array[] = function-like macros in traditional.c: fun_like_macro() when adding more following */ B("__has_attribute", BT_HAS_ATTRIBUTE, true), + B("__has_c_attribute", BT_HAS_STD_ATTRIBUTE, true), B("__has_cpp_attribute", BT_HAS_ATTRIBUTE, true), B("__has_builtin", BT_HAS_BUILTIN, true), B("__has_include", BT_HAS_INCLUDE, true), @@ -492,6 +493,7 @@ cpp_init_special_builtins (cpp_reader *pfile) for (b = builtin_array; b < builtin_array + n; b++) { if ((b->value == BT_HAS_ATTRIBUTE + || b->value == BT_HAS_STD_ATTRIBUTE || b->value == BT_HAS_BUILTIN) && (CPP_OPTION (pfile, lang) == CLK_ASM || pfile->cb.has_attribute == NULL)) diff --git a/libcpp/macro.c b/libcpp/macro.c index e2cb89e4c43..aa16752e2b2 100644 --- a/libcpp/macro.c +++ b/libcpp/macro.c @@ -648,7 +648,11 @@ _cpp_builtin_macro_text (cpp_reader *pfile, cpp_hashnode *node, break; case BT_HAS_ATTRIBUTE: - number = pfile->cb.has_attribute (pfile); + number = pfile->cb.has_attribute (pfile, false); + break; + + case BT_HAS_STD_ATTRIBUTE: + number = pfile->cb.has_attribute (pfile, true); break; case BT_HAS_BUILTIN: diff --git a/libcpp/traditional.c b/libcpp/traditional.c index b087072c9b4..225e3c2c2f2 100644 --- a/libcpp/traditional.c +++ b/libcpp/traditional.c @@ -330,6 +330,7 @@ fun_like_macro (cpp_hashnode *node) { if (cpp_builtin_macro_p (node)) return (node->value.builtin == BT_HAS_ATTRIBUTE + || node->value.builtin == BT_HAS_STD_ATTRIBUTE || node->value.builtin == BT_HAS_BUILTIN || node->value.builtin == BT_HAS_INCLUDE || node->value.builtin == BT_HAS_INCLUDE_NEXT); -- 2.30.2