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);