Hi,

This is a v5 patch to address Marek's feedback here:
https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635157.html

I also implemented Jason's suggestion to use constexpr for the tables
from this review:
https://gcc.gnu.org/pipermail/gcc-patches/2023-October/634484.html

I'll attach the incremental change in reply to Marek's review to make
things easier to compare.

Bootstrapped/regtested on aarch64-linux-gnu.  Bootstrap/regtest on
x86_64-apple-darwin in progress (on top of this libsanitizer fix:
https://github.com/llvm/llvm-project/issues/72639).

OK for trunk if testing passes?

Thanks,
Alex

-- >8 --

This patch implements clang's __has_feature and __has_extension in GCC.
Currently the patch aims to implement all documented features (and some
undocumented ones) following the documentation at
https://clang.llvm.org/docs/LanguageExtensions.html with the exception
of the legacy features for C++ type traits.  These are omitted, since as
the clang documentation notes, __has_builtin is the correct "modern" way
to query for these (which GCC already implements).

gcc/c-family/ChangeLog:

        PR c++/60512
        * c-common.cc (struct hf_feature_info): New.
        (c_common_register_feature): New.
        (init_has_feature): New.
        (has_feature_p): New.
        * c-common.h (c_common_has_feature): New.
        (c_family_register_lang_features): New.
        (c_common_register_feature): New.
        (has_feature_p): New.
        (c_register_features): New.
        (cp_register_features): New.
        * c-lex.cc (init_c_lex): Plumb through has_feature callback.
        (c_common_has_builtin): Generalize and move common part ...
        (c_common_lex_availability_macro): ... here.
        (c_common_has_feature): New.
        * c-ppoutput.cc (init_pp_output): Plumb through has_feature.

gcc/c/ChangeLog:

        PR c++/60512
        * c-lang.cc (c_family_register_lang_features): New.
        * c-objc-common.cc (struct c_feature_info): New.
        (c_register_features): New.

gcc/cp/ChangeLog:

        PR c++/60512
        * cp-lang.cc (c_family_register_lang_features): New.
        * cp-objcp-common.cc (struct cp_feature_selector): New.
        (cp_feature_selector::has_feature): New.
        (struct cp_feature_info): New.
        (cp_register_features): New.

gcc/ChangeLog:

        PR c++/60512
        * doc/cpp.texi: Document __has_{feature,extension}.

gcc/objc/ChangeLog:

        PR c++/60512
        * objc-act.cc (struct objc_feature_info): New.
        (objc_nonfragile_abi_p): New.
        (objc_common_register_features): New.
        * objc-act.h (objc_common_register_features): New.
        * objc-lang.cc (c_family_register_lang_features): New.

gcc/objcp/ChangeLog:

        PR c++/60512
        * objcp-lang.cc (c_family_register_lang_features): New.

libcpp/ChangeLog:

        PR c++/60512
        * include/cpplib.h (struct cpp_callbacks): Add has_feature.
        (enum cpp_builtin_type): Add BT_HAS_{FEATURE,EXTENSION}.
        * init.cc: Add __has_{feature,extension}.
        * macro.cc (_cpp_builtin_macro_text): Handle
        BT_HAS_{FEATURE,EXTENSION}.

gcc/testsuite/ChangeLog:

        PR c++/60512
        * c-c++-common/has-feature-common.c: New test.
        * c-c++-common/has-feature-pedantic.c: New test.
        * g++.dg/ext/has-feature.C: New test.
        * gcc.dg/asan/has-feature-asan.c: New test.
        * gcc.dg/has-feature.c: New test.
        * gcc.dg/ubsan/has-feature-ubsan.c: New test.
        * obj-c++.dg/has-feature.mm: New test.
        * objc.dg/has-feature.m: New test.
diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 0ea0c4f4bef..f270fa2f5b5 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -311,6 +311,44 @@ const struct fname_var_t fname_vars[] =
   {NULL, 0, 0},
 };
 
+/* Flags to restrict availability of generic features that
+   are known to __has_{feature,extension}.  */
+
+enum
+{
+  HF_FLAG_NONE = 0,
+  HF_FLAG_EXT = 1,     /* Available only as an extension.  */
+  HF_FLAG_SANITIZE = 2, /* Availability depends on sanitizer flags.  */
+};
+
+/* Info for generic features which can be queried through
+   __has_{feature,extension}.  */
+
+struct hf_feature_info
+{
+  const char *ident;
+  unsigned flags;
+  unsigned mask;
+};
+
+/* Table of generic features which can be queried through
+   __has_{feature,extension}.  */
+
+static constexpr hf_feature_info has_feature_table[] =
+{
+  { "address_sanitizer",           HF_FLAG_SANITIZE, SANITIZE_ADDRESS },
+  { "thread_sanitizer",                    HF_FLAG_SANITIZE, SANITIZE_THREAD },
+  { "leak_sanitizer",              HF_FLAG_SANITIZE, SANITIZE_LEAK },
+  { "hwaddress_sanitizer",         HF_FLAG_SANITIZE, SANITIZE_HWADDRESS },
+  { "undefined_behavior_sanitizer", HF_FLAG_SANITIZE, SANITIZE_UNDEFINED },
+  { "attribute_deprecated_with_message",  HF_FLAG_NONE, 0 },
+  { "attribute_unavailable_with_message", HF_FLAG_NONE, 0 },
+  { "enumerator_attributes",             HF_FLAG_NONE, 0 },
+  { "tls",                               HF_FLAG_NONE, 0 },
+  { "gnu_asm_goto_with_outputs",         HF_FLAG_EXT, 0 },
+  { "gnu_asm_goto_with_outputs_full",    HF_FLAG_EXT, 0 }
+};
+
 /* Global visibility options.  */
 struct visibility_flags visibility_options;
 
@@ -9877,4 +9915,63 @@ c_strict_flex_array_level_of (tree array_field)
   return strict_flex_array_level;
 }
 
+/* Map from identifiers to booleans.  Value is true for features, and
+   false for extensions.  Used to implement __has_{feature,extension}.  */
+
+using feature_map_t = hash_map <tree, bool>;
+static feature_map_t *feature_map;
+
+/* Register a feature for __has_{feature,extension}.  FEATURE_P is true
+   if the feature identified by NAME is a feature (as opposed to an
+   extension).  */
+
+void
+c_common_register_feature (const char *name, bool feature_p)
+{
+  bool dup = feature_map->put (get_identifier (name), feature_p);
+  gcc_checking_assert (!dup);
+}
+
+/* Lazily initialize hash table for __has_{feature,extension},
+   dispatching to the appropriate front end to register language-specific
+   features.  */
+
+static void
+init_has_feature ()
+{
+  gcc_checking_assert (!feature_map);
+  feature_map = new feature_map_t;
+
+  for (unsigned i = 0; i < ARRAY_SIZE (has_feature_table); i++)
+    {
+      const hf_feature_info *info = has_feature_table + i;
+
+      if ((info->flags & HF_FLAG_SANITIZE) && !(flag_sanitize & info->mask))
+       continue;
+
+      const bool feature_p = !(info->flags & HF_FLAG_EXT);
+      c_common_register_feature (info->ident, feature_p);
+    }
+
+  /* Register language-specific features.  */
+  c_family_register_lang_features ();
+}
+
+/* If STRICT_P is true, evaluate __has_feature (IDENT).
+   Otherwise, evaluate __has_extension (IDENT).  */
+
+bool
+has_feature_p (const char *ident, bool strict_p)
+{
+  if (!feature_map)
+    init_has_feature ();
+
+  tree name = canonicalize_attr_name (get_identifier (ident));
+  bool *feat_p = feature_map->get (name);
+  if (!feat_p)
+    return false;
+
+  return !strict_p || *feat_p;
+}
+
 #include "gt-c-family-c-common.h"
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index b57e83d7c5d..a5359b7caa5 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1126,6 +1126,20 @@ extern bool c_cpp_diagnostic (cpp_reader *, enum 
cpp_diagnostic_level,
      ATTRIBUTE_GCC_DIAG(5,0);
 extern int c_common_has_attribute (cpp_reader *, bool);
 extern int c_common_has_builtin (cpp_reader *);
+extern int c_common_has_feature (cpp_reader *, bool);
+
+/* Implemented by each front end in *-lang.cc.  */
+extern void c_family_register_lang_features ();
+
+/* Implemented in c-family/c-common.cc.  */
+extern void c_common_register_feature (const char *, bool);
+extern bool has_feature_p (const char *, bool);
+
+/* Implemented in c/c-objc-common.cc.  */
+extern void c_register_features ();
+
+/* Implemented in cp/cp-objcp-common.cc.  */
+extern void cp_register_features ();
 
 extern bool parse_optimize_options (tree, bool);
 
diff --git a/gcc/c-family/c-lex.cc b/gcc/c-family/c-lex.cc
index 06c2453c89a..e837c7459d5 100644
--- a/gcc/c-family/c-lex.cc
+++ b/gcc/c-family/c-lex.cc
@@ -82,6 +82,7 @@ init_c_lex (void)
   cb->read_pch = c_common_read_pch;
   cb->has_attribute = c_common_has_attribute;
   cb->has_builtin = c_common_has_builtin;
+  cb->has_feature = c_common_has_feature;
   cb->get_source_date_epoch = cb_get_source_date_epoch;
   cb->get_suggestion = cb_get_suggestion;
   cb->remap_filename = remap_macro_filename;
@@ -451,16 +452,16 @@ c_common_has_attribute (cpp_reader *pfile, bool 
std_syntax)
   return result;
 }
 
-/* Callback for has_builtin.  */
+/* Helper for __has_{builtin,feature,extension}.  */
 
-int
-c_common_has_builtin (cpp_reader *pfile)
+static const char *
+c_common_lex_availability_macro (cpp_reader *pfile, const char *builtin)
 {
   const cpp_token *token = get_token_no_padding (pfile);
   if (token->type != CPP_OPEN_PAREN)
     {
       cpp_error (pfile, CPP_DL_ERROR,
-                "missing '(' after \"__has_builtin\"");
+                "missing '(' after \"__has_%s\"", builtin);
       return 0;
     }
 
@@ -480,7 +481,7 @@ c_common_has_builtin (cpp_reader *pfile)
   else
     {
       cpp_error (pfile, CPP_DL_ERROR,
-                "macro \"__has_builtin\" requires an identifier");
+                "macro \"__has_%s\" requires an identifier", builtin);
       if (token->type == CPP_CLOSE_PAREN)
        return 0;
     }
@@ -499,9 +500,38 @@ c_common_has_builtin (cpp_reader *pfile)
        break;
     }
 
+  return name;
+}
+
+/* Callback for has_builtin.  */
+
+int
+c_common_has_builtin (cpp_reader *pfile)
+{
+  const char *name = c_common_lex_availability_macro (pfile, "builtin");
+  if (!name)
+    return 0;
+
   return names_builtin_p (name);
 }
 
+/* Callback for has_feature.  STRICT_P is true for has_feature and false
+   for has_extension.  */
+
+int
+c_common_has_feature (cpp_reader *pfile, bool strict_p)
+{
+  const char *builtin = strict_p ? "feature" : "extension";
+  const char *name = c_common_lex_availability_macro (pfile, builtin);
+  if (!name)
+    return 0;
+
+  /* If -pedantic-errors is given, __has_extension is equivalent to
+     __has_feature.  */
+  strict_p |= flag_pedantic_errors;
+  return has_feature_p (name, strict_p);
+}
+
 
 /* Read a token and return its type.  Fill *VALUE with its value, if
    applicable.  Fill *CPP_FLAGS with the token's flags, if it is
diff --git a/gcc/c-family/c-ppoutput.cc b/gcc/c-family/c-ppoutput.cc
index 4aa2bef2c0f..a1488c6f086 100644
--- a/gcc/c-family/c-ppoutput.cc
+++ b/gcc/c-family/c-ppoutput.cc
@@ -162,6 +162,7 @@ init_pp_output (FILE *out_stream)
 
   cb->has_attribute = c_common_has_attribute;
   cb->has_builtin = c_common_has_builtin;
+  cb->has_feature = c_common_has_feature;
   cb->get_source_date_epoch = cb_get_source_date_epoch;
   cb->remap_filename = remap_macro_filename;
 
diff --git a/gcc/c/c-lang.cc b/gcc/c/c-lang.cc
index ddfd3e80074..ff6633e4701 100644
--- a/gcc/c/c-lang.cc
+++ b/gcc/c/c-lang.cc
@@ -63,6 +63,15 @@ c_get_sarif_source_language (const char *)
   return "c";
 }
 
+/* Implement c-family hook to register language-specific features for
+   __has_{feature,extension}.  */
+
+void
+c_family_register_lang_features ()
+{
+  c_register_features ();
+}
+
 #if CHECKING_P
 
 namespace selftest {
diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc
index c8f49aa2370..53eda7fa707 100644
--- a/gcc/c/c-objc-common.cc
+++ b/gcc/c/c-objc-common.cc
@@ -34,6 +34,39 @@ along with GCC; see the file COPYING3.  If not see
 static bool c_tree_printer (pretty_printer *, text_info *, const char *,
                            int, bool, bool, bool, bool *, const char **);
 
+/* Info for C language features which can be queried through
+   __has_{feature,extension}.  */
+
+struct c_feature_info
+{
+  const char *ident;
+  const int *enable_flag;
+};
+
+static const c_feature_info c_feature_table[] =
+{
+  { "c_alignas", &flag_isoc11 },
+  { "c_alignof", &flag_isoc11 },
+  { "c_atomic", &flag_isoc11 },
+  { "c_generic_selections", &flag_isoc11 },
+  { "c_static_assert", &flag_isoc11 },
+  { "c_thread_local", &flag_isoc11 },
+  { "cxx_binary_literals", &flag_isoc23 }
+};
+
+/* Register features specific to the C language.  */
+
+void
+c_register_features ()
+{
+  for (unsigned i = 0; i < ARRAY_SIZE (c_feature_table); i++)
+    {
+      const c_feature_info *info = c_feature_table + i;
+      const bool feat_p = !info->enable_flag || *info->enable_flag;
+      c_common_register_feature (info->ident, feat_p);
+    }
+}
+
 bool
 c_missing_noreturn_ok_p (tree decl)
 {
diff --git a/gcc/cp/cp-lang.cc b/gcc/cp/cp-lang.cc
index f2ed83de4fb..bdc25ddd9ff 100644
--- a/gcc/cp/cp-lang.cc
+++ b/gcc/cp/cp-lang.cc
@@ -119,6 +119,15 @@ objcp_tsubst_expr (tree /*t*/, tree /*args*/, 
tsubst_flags_t /*complain*/,
   return NULL_TREE;
 }
 
+/* Implement c-family hook to add language-specific features
+   for __has_{feature,extension}.  */
+
+void
+c_family_register_lang_features ()
+{
+  cp_register_features ();
+}
+
 static const char *
 cxx_dwarf_name (tree t, int verbosity)
 {
diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc
index 2093ae02466..70f9e4ab9e4 100644
--- a/gcc/cp/cp-objcp-common.cc
+++ b/gcc/cp/cp-objcp-common.cc
@@ -23,10 +23,154 @@ along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "cp-tree.h"
 #include "cp-objcp-common.h"
+#include "c-family/c-common.h"
 #include "dwarf2.h"
 #include "stringpool.h"
 #include "contracts.h"
 
+/* Class to determine whether a given C++ language feature is available.
+   Used to implement __has_{feature,extension}.  */
+
+struct cp_feature_selector
+{
+  enum
+  {
+    DIALECT,
+    FLAG
+  } kind;
+
+  enum class result
+  {
+    NONE,
+    EXT,
+    FEAT
+  };
+
+  union
+  {
+    const int *enable_flag;
+    struct {
+      enum cxx_dialect feat;
+      enum cxx_dialect ext;
+    } dialect;
+  };
+
+  constexpr cp_feature_selector (const int *flag)
+    : kind (FLAG), enable_flag (flag) {}
+  constexpr cp_feature_selector (enum cxx_dialect feat,
+                                enum cxx_dialect ext)
+    : kind (DIALECT), dialect{feat, ext} {}
+  constexpr cp_feature_selector (enum cxx_dialect feat)
+    : cp_feature_selector (feat, feat) {}
+
+  inline result has_feature () const;
+};
+
+/* Check whether this language feature is available as a feature,
+   extension, or not at all.  */
+
+cp_feature_selector::result
+cp_feature_selector::has_feature () const
+{
+  switch (kind)
+    {
+    case DIALECT:
+      if (cxx_dialect >= dialect.feat)
+       return result::FEAT;
+      else if (cxx_dialect >= dialect.ext)
+       return result::EXT;
+      else
+       return result::NONE;
+    case FLAG:
+      return *enable_flag ? result::FEAT : result::NONE;
+    }
+
+  gcc_unreachable ();
+}
+
+/* Information about a C++ language feature which can be queried
+   through __has_{feature,extension}.  IDENT is the name of the feature,
+   and SELECTOR encodes how to compute whether the feature is available.  */
+
+struct cp_feature_info
+{
+  const char *ident;
+  cp_feature_selector selector;
+};
+
+/* Table of features for __has_{feature,extension}.  */
+
+static constexpr cp_feature_info cp_feature_table[] =
+{
+  { "cxx_exceptions", &flag_exceptions },
+  { "cxx_rtti", &flag_rtti },
+  { "cxx_access_control_sfinae", { cxx11, cxx98 } },
+  { "cxx_alias_templates", cxx11 },
+  { "cxx_alignas", cxx11 },
+  { "cxx_alignof", cxx11 },
+  { "cxx_attributes", cxx11 },
+  { "cxx_constexpr", cxx11 },
+  { "cxx_constexpr_string_builtins", cxx11 },
+  { "cxx_decltype", cxx11 },
+  { "cxx_decltype_incomplete_return_types", cxx11 },
+  { "cxx_default_function_template_args", cxx11 },
+  { "cxx_defaulted_functions", cxx11 },
+  { "cxx_delegating_constructors", cxx11 },
+  { "cxx_deleted_functions", cxx11 },
+  { "cxx_explicit_conversions", cxx11 },
+  { "cxx_generalized_initializers", cxx11 },
+  { "cxx_implicit_moves", cxx11 },
+  { "cxx_inheriting_constructors", cxx11 },
+  { "cxx_inline_namespaces", { cxx11, cxx98 } },
+  { "cxx_lambdas", cxx11 },
+  { "cxx_local_type_template_args", cxx11 },
+  { "cxx_noexcept", cxx11 },
+  { "cxx_nonstatic_member_init", cxx11 },
+  { "cxx_nullptr", cxx11 },
+  { "cxx_override_control", cxx11 },
+  { "cxx_reference_qualified_functions", cxx11 },
+  { "cxx_range_for", cxx11 },
+  { "cxx_raw_string_literals", cxx11 },
+  { "cxx_rvalue_references", cxx11 },
+  { "cxx_static_assert", cxx11 },
+  { "cxx_thread_local", cxx11 },
+  { "cxx_auto_type", cxx11 },
+  { "cxx_strong_enums", cxx11 },
+  { "cxx_trailing_return", cxx11 },
+  { "cxx_unicode_literals", cxx11 },
+  { "cxx_unrestricted_unions", cxx11 },
+  { "cxx_user_literals", cxx11 },
+  { "cxx_variadic_templates", { cxx11, cxx98 } },
+  { "cxx_binary_literals", { cxx14, cxx98 } },
+  { "cxx_contextual_conversions", { cxx14, cxx98 } },
+  { "cxx_decltype_auto", cxx14 },
+  { "cxx_aggregate_nsdmi", cxx14 },
+  { "cxx_init_captures", cxx14 },
+  { "cxx_generic_lambdas", cxx14 },
+  { "cxx_relaxed_constexpr", cxx14 },
+  { "cxx_return_type_deduction", cxx14 },
+  { "cxx_variable_templates", cxx14 },
+  { "modules", &flag_modules },
+};
+
+/* Register C++ language features for __has_{feature,extension}.  */
+
+void
+cp_register_features ()
+{
+  using result = cp_feature_selector::result;
+
+  for (unsigned i = 0; i < ARRAY_SIZE (cp_feature_table); i++)
+    {
+      const cp_feature_info *info = cp_feature_table + i;
+      const auto res = info->selector.has_feature ();
+      if (res == result::NONE)
+       continue;
+
+      c_common_register_feature (info->ident, res == result::FEAT);
+    }
+}
+
 /* Special routine to get the alias set for C++.  */
 
 alias_set_type
diff --git a/gcc/doc/cpp.texi b/gcc/doc/cpp.texi
index 04f357c74b2..7bfe72b3e4e 100644
--- a/gcc/doc/cpp.texi
+++ b/gcc/doc/cpp.texi
@@ -3199,6 +3199,8 @@ directive}: @samp{#if}, @samp{#ifdef} or @samp{#ifndef}.
 * @code{__has_cpp_attribute}::
 * @code{__has_c_attribute}::
 * @code{__has_builtin}::
+* @code{__has_feature}::
+* @code{__has_extension}::
 * @code{__has_include}::
 @end menu
 
@@ -3561,6 +3563,45 @@ the operator is as follows:
 #endif
 @end smallexample
 
+@node @code{__has_feature}
+@subsection @code{__has_feature}
+@cindex @code{__has_feature}
+
+The special operator @code{__has_feature (@var{operand})} may be used in
+constant integer contexts and in preprocessor @samp{#if} and @samp{#elif}
+expressions to test whether the identifier given in @var{operand} is recognized
+as a feature supported by GCC given the current options and, in the case of
+standard language features, whether the feature is available in the chosen
+version of the language standard.
+
+Note that @code{__has_feature} and @code{__has_extension} are not recommended
+for use in new code, and are only provided for compatibility with Clang.  For
+details of which identifiers are accepted by these function-like macros, see
+@w{@uref{https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension,
+the Clang documentation}}.
+
+@node @code{__has_extension}
+@subsection @code{__has_extension}
+@cindex @code{__has_extension}
+
+The special operator @code{__has_extension (@var{operand})} may be used in
+constant integer contexts and in preprocessor @samp{#if} and @samp{#elif}
+expressions to test whether the identifier given in @var{operand} is recognized
+as an extension supported by GCC given the current options.  In any given
+context, the features accepted by @code{__has_extension} are a strict superset
+of those accepted by @code{__has_feature}.  Unlike @code{__has_feature},
+@code{__has_extension} tests whether a given feature is available regardless of
+strict language standards conformance.
+
+If the @option{-pedantic-errors} flag is given, @code{__has_extension} is
+equivalent to @code{__has_feature}.
+
+Note that @code{__has_feature} and @code{__has_extension} are not recommended
+for use in new code, and are only provided for compatibility with Clang.  For
+details of which identifiers are accepted by these function-like macros, see
+@w{@uref{https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension,
+the Clang documentation}}.
+
 @node @code{__has_include}
 @subsection @code{__has_include}
 @cindex @code{__has_include}
diff --git a/gcc/objc/objc-act.cc b/gcc/objc/objc-act.cc
index fe19aa55277..02ed6d92eb9 100644
--- a/gcc/objc/objc-act.cc
+++ b/gcc/objc/objc-act.cc
@@ -10340,5 +10340,51 @@ objc_common_init_ts (void)
   MARK_TS_TYPED (PROPERTY_REF);
 }
 
+/* Information for Objective-C-specific features known to __has_feature.  */
+
+struct objc_feature_info
+{
+  typedef bool (*predicate_t) ();
+
+  const char *ident;
+  predicate_t predicate;
+
+  constexpr objc_feature_info (const char *name)
+    : ident (name), predicate (nullptr) {}
+  constexpr objc_feature_info (const char *name, predicate_t p)
+    : ident (name), predicate (p) {}
+
+  bool has_feature () const
+    {
+      return predicate ? predicate () : true;
+    }
+};
+
+static bool objc_nonfragile_abi_p ()
+{
+  return flag_next_runtime && flag_objc_abi >= 2;
+}
+
+static constexpr objc_feature_info objc_features[] =
+{
+  { "objc_default_synthesize_properties" },
+  { "objc_instancetype" },
+  { "objc_nonfragile_abi", objc_nonfragile_abi_p }
+};
+
+/* Register Objective-C-specific features for __has_feature.  */
+
+void
+objc_common_register_features ()
+{
+  for (unsigned i = 0; i < ARRAY_SIZE (objc_features); i++)
+    {
+      const objc_feature_info *info = objc_features + i;
+      if (!info->has_feature ())
+       continue;
+
+      c_common_register_feature (info->ident, true);
+    }
+}
 
 #include "gt-objc-objc-act.h"
diff --git a/gcc/objc/objc-act.h b/gcc/objc/objc-act.h
index 68f90296db3..df40127ab9c 100644
--- a/gcc/objc/objc-act.h
+++ b/gcc/objc/objc-act.h
@@ -29,6 +29,9 @@ int objc_gimplify_expr (tree *, gimple_seq *, gimple_seq *);
 void objc_common_init_ts (void);
 const char *objc_get_sarif_source_language (const char *);
 
+/* Register features common to Objective-C and Objective-C++.  */
+void objc_common_register_features ();
+
 /* NB: The remaining public functions are prototyped in c-common.h, for the
    benefit of stub-objc.cc and objc-act.cc.  */
 
diff --git a/gcc/objc/objc-lang.cc b/gcc/objc/objc-lang.cc
index 8e56de6a81c..107fb0e8138 100644
--- a/gcc/objc/objc-lang.cc
+++ b/gcc/objc/objc-lang.cc
@@ -56,6 +56,16 @@ objc_get_sarif_source_language (const char *)
   return "objectivec";
 }
 
+/* Implement c-family hook to add language-specific features
+   for __has_{feature,extension}.  */
+
+void
+c_family_register_lang_features ()
+{
+  objc_common_register_features ();
+  c_register_features ();
+}
+
 /* Lang hook routines common to C and ObjC appear in c-objc-common.cc;
    there should be very few (if any) routines below.  */
 
diff --git a/gcc/objcp/objcp-lang.cc b/gcc/objcp/objcp-lang.cc
index ee39aece848..8a2c9f74250 100644
--- a/gcc/objcp/objcp-lang.cc
+++ b/gcc/objcp/objcp-lang.cc
@@ -85,6 +85,16 @@ objcp_tsubst_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
 #undef RECURSE
 }
 
+/* Implement c-family hook to add language-specific features
+   for __has_{feature,extension}.  */
+
+void
+c_family_register_lang_features ()
+{
+  objc_common_register_features ();
+  cp_register_features ();
+}
+
 static void
 objcxx_init_ts (void)
 {
diff --git a/gcc/testsuite/c-c++-common/has-feature-common.c 
b/gcc/testsuite/c-c++-common/has-feature-common.c
new file mode 100644
index 00000000000..1604d7790fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/has-feature-common.c
@@ -0,0 +1,73 @@
+/* { dg-do compile } */
+/* Test __has_{feature,extension} for generic features.  */
+
+#define FEAT(x) (__has_feature (x) && __has_extension (x))
+#define EXT(x) (__has_extension (x) && !__has_feature (x))
+
+#if __has_feature (unknown_feature) || __has_extension (unknown_feature)
+#error unknown feature is known!
+#endif
+
+#if !EXT (gnu_asm_goto_with_outputs)
+#error
+#endif
+
+#if !EXT (__gnu_asm_goto_with_outputs__)
+#error
+#endif
+
+#if !EXT (gnu_asm_goto_with_outputs_full)
+#error
+#endif
+
+#if !EXT (__gnu_asm_goto_with_outputs_full__)
+#error
+#endif
+
+#if !FEAT (enumerator_attributes)
+#error
+#endif
+
+#if !FEAT (__enumerator_attributes__)
+#error
+#endif
+
+#if !FEAT (attribute_deprecated_with_message)
+#error
+#endif
+
+#if !FEAT (__attribute_deprecated_with_message__)
+#error
+#endif
+
+#if !FEAT (attribute_unavailable_with_message)
+#error
+#endif
+
+#if !FEAT (__attribute_unavailable_with_message__)
+#error
+#endif
+
+#if !FEAT (tls)
+#error
+#endif
+
+#if !FEAT(__tls__)
+#error
+#endif
+
+#if defined (__SANITIZE_ADDRESS__) != __has_feature (address_sanitizer)
+#error
+#endif
+
+#if defined (__SANITIZE_ADDRESS__) != __has_extension (address_sanitizer)
+#error
+#endif
+
+#if defined (__SANITIZE_THREAD__) != __has_feature (thread_sanitizer)
+#error
+#endif
+
+#if defined (__SANITIZE_THREAD__) != __has_extension (thread_sanitizer)
+#error
+#endif
diff --git a/gcc/testsuite/c-c++-common/has-feature-pedantic.c 
b/gcc/testsuite/c-c++-common/has-feature-pedantic.c
new file mode 100644
index 00000000000..4ac16a4ed8a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/has-feature-pedantic.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-additional-options "-pedantic-errors" } */
+
+/* When -pedantic-errors is passed, __has_extension should behave like
+   __has_feature.  */
+
+#if __has_feature (gnu_asm_goto_with_outputs)
+#error extension recognized as feature
+#endif
+
+#if __has_extension (gnu_asm_goto_with_outputs)
+#error pure extensions should not be recognized with -pedantic-errors
+#endif
+
+#if !__has_feature (tls) || !__has_extension (tls)
+#error features should still be recognized with -pedantic-errors
+#endif
+
+/* Make this TU non-empty to appease -pedantic-errors.  */
+int foo;
diff --git a/gcc/testsuite/g++.dg/ext/has-feature.C 
b/gcc/testsuite/g++.dg/ext/has-feature.C
new file mode 100644
index 00000000000..52191b78fd6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/has-feature.C
@@ -0,0 +1,206 @@
+// { dg-do compile }
+// { dg-options "" }
+
+#define FEAT(x) (__has_feature(x) && __has_extension(x))
+#define CXX11 (__cplusplus >= 201103L)
+#define CXX14 (__cplusplus >= 201402L)
+
+#if !FEAT(cxx_exceptions) || !FEAT(cxx_rtti)
+#error
+#endif
+
+#if __has_feature (cxx_access_control_sfinae) != CXX11
+#error
+#endif
+
+#if !__has_extension (cxx_access_control_sfinae)
+#error
+#endif
+
+#if FEAT(cxx_alias_templates) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_alignas) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_alignof) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_attributes) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_constexpr) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_decltype) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_decltype_incomplete_return_types) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_default_function_template_args) != CXX11
+#error
+#endif
+
+#if FEAT(cxx_defaulted_functions) != CXX11
+#error
+#endif
+
+#if __has_feature (cxx_delegating_constructors) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_deleted_functions) != CXX11
+#error
+#endif
+
+#if __has_feature (cxx_explicit_conversions) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_generalized_initializers) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_implicit_moves) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_inheriting_constructors) != CXX11
+#error
+#endif
+
+#if !__has_extension (cxx_inline_namespaces)
+#error
+#endif
+
+#if __has_feature (cxx_inline_namespaces) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_lambdas) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_local_type_template_args) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_noexcept) != CXX11
+#error
+#endif
+
+#if __has_feature (cxx_nonstatic_member_init) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_nullptr) != CXX11
+#error
+#endif
+
+#if __has_feature (cxx_override_control) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_reference_qualified_functions) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_range_for) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_raw_string_literals) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_rvalue_references) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_static_assert) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_thread_local) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_auto_type) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_strong_enums) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_trailing_return) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_unicode_literals) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_unrestricted_unions) != CXX11
+#error
+#endif
+
+#if FEAT (cxx_user_literals) != CXX11
+#error
+#endif
+
+#if !__has_extension (cxx_variadic_templates)
+#error
+#endif
+
+#if __has_feature (cxx_variadic_templates) != CXX11
+#error
+#endif
+
+#if !__has_extension (cxx_binary_literals)
+#error
+#endif
+
+#if __has_feature (cxx_binary_literals) != CXX14
+#error
+#endif
+
+#if FEAT (cxx_decltype_auto) != CXX14
+#error
+#endif
+
+#if FEAT (cxx_aggregate_nsdmi) != CXX14
+#error
+#endif
+
+#if __has_extension (cxx_init_captures) != CXX11
+#error
+#endif
+
+#if __has_feature (cxx_init_captures) != CXX14
+#error
+#endif
+
+#if FEAT (cxx_generic_lambdas) != CXX14
+#error
+#endif
+
+#if FEAT (cxx_relaxed_constexpr) != CXX14
+#error
+#endif
+
+#if FEAT (cxx_return_type_deduction) != CXX14
+#error
+#endif
+
+#if __has_feature (cxx_variable_templates) != CXX14
+#error
+#endif
diff --git a/gcc/testsuite/gcc.dg/asan/has-feature-asan.c 
b/gcc/testsuite/gcc.dg/asan/has-feature-asan.c
new file mode 100644
index 00000000000..810b69b8fc8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/asan/has-feature-asan.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=address" } */
+#define FEAT(x) (__has_feature (x) && __has_extension (x))
+#if !FEAT (address_sanitizer)
+#error
+#endif
diff --git a/gcc/testsuite/gcc.dg/has-feature.c 
b/gcc/testsuite/gcc.dg/has-feature.c
new file mode 100644
index 00000000000..2fd0b4c7f1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/has-feature.c
@@ -0,0 +1,62 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+/* Test __has_{feature,extension} for C language features.  */
+
+#if !__has_extension (c_alignas) || !__has_extension (c_alignof)
+#error
+#endif
+
+#if !__has_extension (c_atomic) || !__has_extension (c_generic_selections)
+#error
+#endif
+
+#if !__has_extension (c_static_assert) || !__has_extension (c_thread_local)
+#error
+#endif
+
+#if !__has_extension (cxx_binary_literals)
+#error
+#endif
+
+#if  __STDC_VERSION__ >= 201112L
+/* Have C11 features.  */
+#if !__has_feature (c_alignas) || !__has_feature (c_alignof)
+#error
+#endif
+
+#if !__has_feature (c_atomic) || !__has_feature (c_generic_selections)
+#error
+#endif
+
+#if !__has_feature (c_static_assert) || !__has_feature (c_thread_local)
+#error
+#endif
+
+#else
+/* Don't have C11 features.  */
+#if __has_feature (c_alignas) || __has_feature (c_alignof)
+#error
+#endif
+
+#if __has_feature (c_atomic) || __has_feature (c_generic_selections)
+#error
+#endif
+
+#if __has_feature (c_static_assert) || __has_feature (c_thread_local)
+#error
+#endif
+
+#endif
+
+#if __STDC_VERSION__ >= 202000L
+/* Have C2x features.  */
+#if !__has_feature (cxx_binary_literals)
+#error
+#endif
+
+#else
+/* Don't have C2x features.  */
+#if __has_feature (cxx_binary_literals)
+#error
+#endif
+#endif
diff --git a/gcc/testsuite/gcc.dg/ubsan/has-feature-ubsan.c 
b/gcc/testsuite/gcc.dg/ubsan/has-feature-ubsan.c
new file mode 100644
index 00000000000..e5da1cc5628
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/ubsan/has-feature-ubsan.c
@@ -0,0 +1,6 @@
+/* { dg-do compile } */
+/* { dg-options "-fsanitize=undefined" } */
+#define FEAT(x) (__has_feature (x) && __has_extension (x))
+#if !FEAT (undefined_behavior_sanitizer)
+#error
+#endif
diff --git a/gcc/testsuite/obj-c++.dg/has-feature.mm 
b/gcc/testsuite/obj-c++.dg/has-feature.mm
new file mode 100644
index 00000000000..77c76173bfb
--- /dev/null
+++ b/gcc/testsuite/obj-c++.dg/has-feature.mm
@@ -0,0 +1,21 @@
+// { dg-do compile }
+
+#define CXX11 (__cplusplus >= 201103L)
+
+#if !__has_feature (objc_instancetype)
+#error
+#endif
+
+#if !__has_feature (objc_default_synthesize_properties)
+#error
+#endif
+
+// C features should not be available.
+#if __has_extension (c_alignas) || __has_feature (c_alignof)
+#error
+#endif
+
+// C++ features should be available (given the right standard).
+#if __has_feature (cxx_constexpr) != CXX11
+#error
+#endif
diff --git a/gcc/testsuite/objc.dg/has-feature.m 
b/gcc/testsuite/objc.dg/has-feature.m
new file mode 100644
index 00000000000..168b0ce16e7
--- /dev/null
+++ b/gcc/testsuite/objc.dg/has-feature.m
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+
+#define HAVE_C11 (__STDC_VERSION__ >= 201112L)
+
+#if !__has_feature (objc_instancetype)
+#error
+#endif
+
+#if !__has_feature (objc_default_synthesize_properties)
+#error
+#endif
+
+/* C features should be available as extensions.  */
+#if !__has_extension (c_alignas)
+#error
+#endif
+
+/* And as features given the appropriate C standard.  */
+#if __has_feature (c_alignas) != HAVE_C11
+#error
+#endif
+
+/* Shouldn't have C++ features even as extensions.  */
+#if __has_feature (cxx_constexpr) || __has_extension (cxx_constexpr)
+#error
+#endif
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 54814f335d7..0f4c71c48c6 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -762,6 +762,9 @@ struct cpp_callbacks
   /* Callback to determine whether a built-in function is recognized.  */
   int (*has_builtin) (cpp_reader *);
 
+  /* Callback to determine whether a feature is available.  */
+  int (*has_feature) (cpp_reader *, bool);
+
   /* Callback that can change a user lazy into normal macro.  */
   void (*user_lazy_macro) (cpp_reader *, cpp_macro *, unsigned);
 
@@ -966,7 +969,9 @@ enum cpp_builtin_type
   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)' */
+  BT_HAS_INCLUDE_NEXT,         /* `__has_include_next(x)' */
+  BT_HAS_FEATURE,              /* `__has_feature(x)' */
+  BT_HAS_EXTENSION             /* `__has_extension(x)' */
 };
 
 #define CPP_HASHNODE(HNODE)    ((cpp_hashnode *) (HNODE))
diff --git a/libcpp/init.cc b/libcpp/init.cc
index 250a02aa993..5dfce4bd7a0 100644
--- a/libcpp/init.cc
+++ b/libcpp/init.cc
@@ -435,6 +435,8 @@ static const struct builtin_macro builtin_array[] =
   B("__has_builtin",    BT_HAS_BUILTIN,   true),
   B("__has_include",    BT_HAS_INCLUDE,   true),
   B("__has_include_next",BT_HAS_INCLUDE_NEXT,   true),
+  B("__has_feature",    BT_HAS_FEATURE, true),
+  B("__has_extension",  BT_HAS_EXTENSION, true),
   /* Keep builtins not used for -traditional-cpp at the end, and
      update init_builtins() if any more are added.  */
   B("_Pragma",          BT_PRAGMA,        true),
diff --git a/libcpp/macro.cc b/libcpp/macro.cc
index a1a134ab3a0..7ba77fe88b3 100644
--- a/libcpp/macro.cc
+++ b/libcpp/macro.cc
@@ -677,6 +677,12 @@ _cpp_builtin_macro_text (cpp_reader *pfile, cpp_hashnode 
*node,
       number = builtin_has_include (pfile, node,
                                    node->value.builtin == BT_HAS_INCLUDE_NEXT);
       break;
+
+    case BT_HAS_FEATURE:
+    case BT_HAS_EXTENSION:
+      number = pfile->cb.has_feature (pfile,
+                                     node->value.builtin == BT_HAS_FEATURE);
+      break;
     }
 
   if (result == NULL)

Reply via email to