diff mbox series

c: C2x __has_c_attribute

Message ID alpine.DEB.2.22.394.2011122114260.83821@digraph.polyomino.org.uk
State New
Headers show
Series c: C2x __has_c_attribute | expand

Commit Message

Joseph Myers Nov. 12, 2020, 9:15 p.m. UTC
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.  Applied to 
mainline.

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.
diff mbox series

Patch

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