c: C2x __has_c_attribute
authorJoseph Myers <joseph@codesourcery.com>
Thu, 12 Nov 2020 21:13:51 +0000 (21:13 +0000)
committerJoseph Myers <joseph@codesourcery.com>
Thu, 12 Nov 2020 21:13:51 +0000 (21:13 +0000)
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  <joseph@codesourcery.com>

* doc/cpp.texi (__has_attribute): Document when scopes are allowed
for C.
(__has_c_attribute): New.

gcc/c-family/
2020-11-12  Joseph Myers  <joseph@codesourcery.com>

* 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  <joseph@codesourcery.com>

* 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  <joseph@codesourcery.com>

* 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
gcc/c-family/c-lex.c
gcc/doc/cpp.texi
gcc/testsuite/gcc.dg/c2x-has-c-attribute-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c2x-has-c-attribute-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c2x-has-c-attribute-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/c2x-has-c-attribute-4.c [new file with mode: 0644]
libcpp/include/cpplib.h
libcpp/init.c
libcpp/macro.c
libcpp/traditional.c

index 94f4868915a2120ccb01a025a69eb94ca7602c42..f47097442ebab0b65cf7c05216305cf3f7ef81aa 100644 (file)
@@ -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);
index e81e16ddc26bf1250c8ae5945b270f57863c2fd5..6cd3df7c96f1785f1ed34a7f08f4923cafc9950e 100644 (file)
@@ -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);
index 33f876ab706a944008e5c978b747049d4ff0cbce..291e14676be00e7e95473d5c8ff46589c44e35b2 100644 (file)
@@ -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 (file)
index 0000000..fe06abf
--- /dev/null
@@ -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 (file)
index 0000000..d6c4c6d
--- /dev/null
@@ -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 (file)
index 0000000..36842ed
--- /dev/null
@@ -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 (file)
index 0000000..acd35d2
--- /dev/null
@@ -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
index c4d7cc520d1d8741b0ed33f31b93c9de5942206a..8900e77c6e5e4c7522675b067e69947a8ec4cc52 100644 (file)
@@ -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)' */
index dcf1d4be58724bd452aa67d3b77d5a74fe0212d8..1b43802c29c34f3dad6a70743105d9461c045e3b 100644 (file)
@@ -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))
index e2cb89e4c4379708234bff98c71edda71fa53505..aa16752e2b2b4c4819459dfaed5b744ec2ea2912 100644 (file)
@@ -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:
index b087072c9b4ced7a85a15dc53110d0037ce03dad..225e3c2c2f2127d22030963ac40ab8a9006a4552 100644 (file)
@@ -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);