This implements the handling of the clang-form "availability"
attribute, which is the most important case used in the the macOS
SDKs.

        PR  c++/109877

gcc/ChangeLog:

        * config/darwin-protos.h
        (darwin_handle_weak_import_attribute): New.
        (darwin_handle_availability_attribute): New.
        (darwin_attribute_takes_identifier_p): New.
        * config/darwin.cc (objc_method_decl): New.
        (enum version_components): New.
        (parse_version): New.
        (version_from_version_array): New.
        (os_version_from_avail_value): New.
        (NUM_AV_OSES, NUM_AV_CLAUSES): New.
        (darwin_handle_availability_attribute): New.
        (darwin_attribute_takes_identifier_p): New.
        (darwin_override_options): New.
        * config/darwin.h (TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P): New.

Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>
---
 gcc/config/darwin-protos.h |   9 +-
 gcc/config/darwin.cc       | 343 +++++++++++++++++++++++++++++++++++++
 gcc/config/darwin.h        |   7 +-
 3 files changed, 355 insertions(+), 4 deletions(-)

diff --git a/gcc/config/darwin-protos.h b/gcc/config/darwin-protos.h
index 9df358ee7d3..0702db25178 100644
--- a/gcc/config/darwin-protos.h
+++ b/gcc/config/darwin-protos.h
@@ -86,9 +86,12 @@ extern void darwin_asm_lto_end (void);
 extern void darwin_mark_decl_preserved (const char *);
 
 extern tree darwin_handle_kext_attribute (tree *, tree, tree, int, bool *);
-extern tree darwin_handle_weak_import_attribute (tree *node, tree name,
-                                                tree args, int flags,
-                                                bool * no_add_attrs);
+extern tree darwin_handle_weak_import_attribute (tree *, tree, tree, int,
+                                                bool *);
+extern tree darwin_handle_availability_attribute (tree *, tree, tree,
+                                                 int, bool *);
+extern bool darwin_attribute_takes_identifier_p (const_tree);
+
 extern void machopic_output_stub (FILE *, const char *, const char *);
 extern void darwin_globalize_label (FILE *, const char *);
 extern void darwin_assemble_visibility (tree, int);
diff --git a/gcc/config/darwin.cc b/gcc/config/darwin.cc
index 621a94d74a2..8bb4a439996 100644
--- a/gcc/config/darwin.cc
+++ b/gcc/config/darwin.cc
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cfghooks.h"
 #include "df.h"
 #include "memmodel.h"
+#include "c-family/c-common.h"  /* enum rid.  */
 #include "tm_p.h"
 #include "stringpool.h"
 #include "attribs.h"
@@ -49,6 +50,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "optabs.h"
 #include "flags.h"
 #include "opts.h"
+#include "c-family/c-objc.h"    /* for objc_method_decl().  */
 
 /* Fix and Continue.
 
@@ -102,6 +104,7 @@ int darwin_running_cxx;
 
 /* Some code-gen now depends on OS major version numbers (at least).  */
 int generating_for_darwin_version ;
+unsigned long current_os_version = 0;
 
 /* For older linkers we need to emit special sections (marked 'coalesced') for
    for weak or single-definition items.  */
@@ -2181,6 +2184,96 @@ darwin_handle_kext_attribute (tree *node, tree name,
   return NULL_TREE;
 }
 
+enum version_components { MAJOR, MINOR, TINY };
+
+/* Parse a version number in x.y.z form and validate it as a macOS
+   version.  Ideally, we'd put this in a common place usable by the
+   Darwin backend.  */
+
+static bool
+parse_version (unsigned version_array[3], const char *version_str)
+{
+  size_t version_len;
+  char *end;
+
+  version_len = strlen (version_str);
+  if (version_len < 1)
+    return false;
+
+  /* Version string must consist of digits and periods only.  */
+  if (strspn (version_str, "0123456789.") != version_len)
+    return false;
+
+  if (!ISDIGIT (version_str[0]) || !ISDIGIT (version_str[version_len - 1]))
+    return false;
+
+  version_array[MAJOR] = strtoul (version_str, &end, 10);
+  version_str = end + ((*end == '.') ? 1 : 0);
+  if (version_array[MAJOR]  > 99)
+    return false;
+
+  /* Version string must not contain adjacent periods.  */
+  if (*version_str == '.')
+    return false;
+
+  version_array[MINOR] = strtoul (version_str, &end, 10);
+  version_str = end + ((*end == '.') ? 1 : 0);
+  if (version_array[MINOR]  > 99)
+    return false;
+
+  version_array[TINY] = strtoul (version_str, &end, 10);
+  if (version_array[TINY]  > 99)
+    return false;
+
+  /* Version string must contain no more than three tokens.  */
+  if (*end != '\0')
+    return false;
+
+  return true;
+}
+
+/* Turn a version expressed as maj.min.tiny into an unsigned long
+   integer representing the value used in macOS availability macros.  */
+
+static unsigned long
+version_from_version_array (unsigned vers[3])
+{
+  unsigned long res = 0;
+  /* Here, we follow the 'modern' / 'legacy' numbering scheme for versions.  */
+  if (vers[0] > 10 || vers[1] >= 10)
+    res = vers[0] * 10000 + vers[1] * 100 + vers[2];
+  else
+    {
+      res = vers[0] * 100;
+      if (vers[1] > 9)
+       res += 90;
+      else
+       res += vers[1] * 10;
+      if (vers[2] > 9)
+       res += 9;
+      else
+       res += vers[1];
+    }
+  return res;
+}
+
+/* Extract a macOS version from an availability attribute argument.  */
+
+static unsigned long
+os_version_from_avail_value (tree value)
+{
+  unsigned long res = 0;
+  unsigned vers[3] = {0,0,0};
+  if (TREE_CODE (value) == STRING_CST)
+    {
+      if (parse_version (&vers[0], TREE_STRING_POINTER (value)))
+       res = version_from_version_array (&vers[0]);
+    }
+  else
+    gcc_unreachable ();
+  return res;
+}
+
 /* Handle a "weak_import" attribute; arguments as in
    struct attribute_spec.handler.  */
 
@@ -2202,6 +2295,249 @@ darwin_handle_weak_import_attribute (tree *node, tree 
name,
   return NULL_TREE;
 }
 
+#define NUM_AV_OSES 12
+const char *availability_os[NUM_AV_OSES]
+  = { "macos", "macosx", "ios", "tvos", "watchos", "driverkit", "swift",
+      "maccatalyst", "xros", "visionos", "android", "zos" };
+
+#define NUM_AV_CLAUSES 6
+const char *availability_clause[NUM_AV_CLAUSES]
+  = { "unavailable", "introduced", "deprecated", "obsoleted", "message",
+      "replacement" };
+
+/* Validate and act upon the arguments to an 'availability' attribute.  */
+
+tree
+darwin_handle_availability_attribute (tree *node, tree name, tree args,
+                                     int flags, bool * no_add_attrs)
+{
+  tree decl = *node;
+
+  if (! VAR_OR_FUNCTION_DECL_P (decl))
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored",
+              name);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  location_t loc = DECL_SOURCE_LOCATION (decl);
+  if (args == NULL_TREE)
+    {
+      error_at (loc, "%qE attribute requires at least one argument",
+               name);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* The first argument must name a supported OS - although we could choose
+     to ignore any OS we don't recognise.  */
+  gcc_checking_assert (TREE_CODE (args) == TREE_LIST);
+  tree platform = TREE_VALUE (args);
+  gcc_checking_assert (TREE_CODE (platform) == IDENTIFIER_NODE);
+  bool platform_ok = false;
+  unsigned plat_num = 0;
+  for (; plat_num < (unsigned) NUM_AV_OSES; plat_num++)
+    if (strcmp (availability_os[plat_num], IDENTIFIER_POINTER (platform)) == 0)
+      {
+       platform_ok = true;
+       break;
+      }
+  if (!platform_ok)
+    {
+      error_at (input_location,
+               "platform %qE is not recognised for the %<availability%> "
+               "attribute", platform);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+  else if (plat_num > 1) /* We only compile for macos so far.  */
+    {
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* We might be dealing with an object or type.  */
+  tree target_decl = NULL_TREE;
+  tree type = NULL_TREE;
+  bool warn = false;
+  if (DECL_P (*node))
+    {
+      type = TREE_TYPE (decl);
+
+      if (TREE_CODE (decl) == TYPE_DECL
+         || TREE_CODE (decl) == PARM_DECL
+         || VAR_OR_FUNCTION_DECL_P (decl)
+         || TREE_CODE (decl) == FIELD_DECL
+         || TREE_CODE (decl) == CONST_DECL
+         /*|| objc_method_decl (TREE_CODE (decl))*/)
+       target_decl = decl;
+      else
+       warn = true;
+    }
+  else if (TYPE_P (*node))
+    type = target_decl = *node;
+  else
+    warn = true;
+
+  tree what = NULL_TREE;
+  if (warn)
+    {
+      *no_add_attrs = true;
+      if (type && TYPE_NAME (type))
+       {
+         if (TREE_CODE (TYPE_NAME (type)) == IDENTIFIER_NODE)
+           what = TYPE_NAME (*node);
+         else if (TREE_CODE (TYPE_NAME (type)) == TYPE_DECL
+                  && DECL_NAME (TYPE_NAME (type)))
+           what = DECL_NAME (TYPE_NAME (type));
+       }
+      if (what)
+       warning (OPT_Wattributes, "%qE attribute ignored for %qE", name, what);
+      else
+       warning (OPT_Wattributes, "%qE attribute ignored", name);
+      return NULL_TREE;
+    }
+
+  /* Now we have to parse the availability clauses.  */
+  tree msg = NULL_TREE;
+  tree replacement = NULL_TREE;
+  bool unavailable = false;
+  unsigned introduced = 1000;
+  unsigned deprecated = current_os_version + 1;
+  unsigned obsoleted = current_os_version + 1;
+  for (tree arg = TREE_CHAIN (args); arg; arg = TREE_CHAIN (arg))
+    {
+      tree clause_name = TREE_VALUE (arg);
+      tree clause_value = TREE_PURPOSE (arg);
+      unsigned clause_num = 0;
+      for (; clause_num < (unsigned) NUM_AV_CLAUSES; clause_num++)
+       if (strcmp (availability_clause[clause_num],
+                   IDENTIFIER_POINTER (clause_name)) == 0)
+         break;
+      switch (clause_num)
+        {
+       default:
+         {
+           error_at (input_location,
+                     "clause %qE is not recognised for the %<availability%> "
+                     "attribute", clause_name);
+           *no_add_attrs = true;
+           return NULL_TREE;
+         }
+         break;
+       case 0:
+         unavailable = true;
+         break;
+       case 1:
+       case 2:
+       case 3:
+         if (!clause_value)
+           {
+             error ("%qs= requires a value",
+                    clause_num > 2 ? "obsoleted"
+                                   : clause_num > 1 ? "deprecated"
+                                                    : "introduced");
+             *no_add_attrs = true;
+           }
+         else
+           {
+             unsigned version = os_version_from_avail_value (clause_value);
+             if (version == 0)
+               {
+                 error ("the value %qE provided to %qs is not a "
+                        "valid OS version", clause_value,
+                        (clause_num > 2 ? "obsoleted"
+                                        : clause_num > 1 ? "deprecated"
+                                                         : "introduced"));
+                 *no_add_attrs = true;
+               }
+             else if (clause_num == 1)
+               introduced = version;
+             else if (clause_num == 2)
+               deprecated = version;
+             else if (clause_num == 3)
+               obsoleted = version;
+           }
+         break;
+       case 4:
+         if (TREE_CODE (clause_value) != STRING_CST)
+           {
+             error ("%<message=%> requires a string");
+             *no_add_attrs = true;
+           }
+         else
+           msg = clause_value;
+         break;
+       case 5:
+         if (TREE_CODE (clause_value) != STRING_CST)
+           {
+             error ("%<replacement=%> requires a string");
+             *no_add_attrs = true;
+           }
+         else
+           replacement = clause_value;
+         break;
+       }
+    }
+  /* Now figure out what to do.  */
+  if (unavailable
+      || current_os_version >= obsoleted
+      || current_os_version < introduced)
+    {
+      tree new_attr = NULL_TREE;
+      if (replacement)
+       new_attr
+         = tree_cons (get_identifier ("unavailable"),
+                      tree_cons (NULL_TREE, replacement, NULL_TREE), 
NULL_TREE);
+      else if (msg)
+       new_attr
+         = tree_cons (get_identifier ("unavailable"),
+                      tree_cons (NULL_TREE, msg, NULL_TREE), NULL_TREE);
+      /* This is the actual consequence of the evaluation.  */
+      if (TYPE_P (target_decl) && !(flags & (int) ATTR_FLAG_TYPE_IN_PLACE))
+       {
+         *node = build_variant_type_copy (*node);
+         TYPE_ATTRIBUTES (*node) = chainon (TYPE_ATTRIBUTES (*node), new_attr);
+       }
+      else
+       DECL_ATTRIBUTES (*node) = chainon (DECL_ATTRIBUTES (*node), new_attr);
+//      *no_add_attrs = true; /* maybe we should add to detect conflicts.  */
+      TREE_UNAVAILABLE (*node) = true;
+      return NULL_TREE;
+    }
+   else if (current_os_version > deprecated)
+    {
+      tree new_attr = NULL_TREE;
+      if (replacement)
+       new_attr
+         = tree_cons (get_identifier ("deprecated"),
+                      tree_cons (NULL_TREE, replacement, NULL_TREE), 
NULL_TREE);
+      else if (msg)
+       new_attr
+         = tree_cons (get_identifier ("deprecated"),
+                      tree_cons (NULL_TREE, msg, NULL_TREE), NULL_TREE);
+      /* This is the actual consequence of the evaluation.  */
+      if (TYPE_P (target_decl) && !(flags & (int) ATTR_FLAG_TYPE_IN_PLACE))
+       {
+         *node = build_variant_type_copy (*node);
+         TYPE_ATTRIBUTES (*node) = chainon (TYPE_ATTRIBUTES (*node), new_attr);
+       }
+      else
+       DECL_ATTRIBUTES (*node) = chainon (DECL_ATTRIBUTES (*node), new_attr);
+//      *no_add_attrs = true; /* maybe we should add to detect conflicts.  */
+      TREE_DEPRECATED (*node) = true;
+      return NULL_TREE;
+    }
+ return NULL_TREE;
+}
+
+bool
+darwin_attribute_takes_identifier_p (const_tree attr_id)
+{
+  return is_attribute_p ("availability", attr_id);
+}
+
 /* Emit a label for an FDE, making it global and/or weak if appropriate.
    The third parameter is nonzero if this is for exception handling.
    The fourth parameter is nonzero if this is just a placeholder for an
@@ -3337,7 +3673,14 @@ darwin_override_options (void)
        generating_for_darwin_version = 8;
 
       /* Earlier versions are not specifically accounted, until required.  */
+      unsigned vers[3] = {0,0,0};
+      if (!parse_version (vers, darwin_macosx_version_min))
+       error_at (UNKNOWN_LOCATION, "how did we get a bad OS version? (%s)",
+                 darwin_macosx_version_min);
+      current_os_version = version_from_version_array (vers);
     }
+  else
+    current_os_version = 1058;
 
   /* Some codegen needs to account for the capabilities of the target
      linker.  */
diff --git a/gcc/config/darwin.h b/gcc/config/darwin.h
index 5db64a1ad68..684c754ce0f 100644
--- a/gcc/config/darwin.h
+++ b/gcc/config/darwin.h
@@ -963,7 +963,12 @@ extern GTY(()) section * 
darwin_sections[NUM_DARWIN_SECTIONS];
   { "apple_kext_compatibility", 0, 0, false, true, false, false,            \
     darwin_handle_kext_attribute, NULL },                                   \
   { "weak_import", 0, 0, true, false, false, false,                         \
-    darwin_handle_weak_import_attribute, NULL }
+    darwin_handle_weak_import_attribute, NULL },                            \
+  { "availability", 0, -1, true, false, false, false,                       \
+    darwin_handle_availability_attribute, NULL }
+
+#undef TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P
+#define TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P darwin_attribute_takes_identifier_p
 
 /* Make local constant labels linker-visible, so that if one follows a
    weak_global constant, ld64 will be able to separate the atoms.  */
-- 
2.39.2 (Apple Git-143)

Reply via email to