[clang] [clang][ExtractAPI] Compute inherited availability information (PR #103040)

Daniel Grumberg via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 14 03:05:04 PDT 2024


https://github.com/daniel-grumberg updated https://github.com/llvm/llvm-project/pull/103040

>From cd38c476336ea90e4d080638d028dda203b52ac4 Mon Sep 17 00:00:00 2001
From: Daniel Grumberg <dgrumberg at apple.com>
Date: Tue, 13 Aug 2024 11:30:18 +0100
Subject: [PATCH 1/5] [clang][ExtractAPI] Compute inherited availability
 information

Additionally this computes availability information for all platforms
ahead of possibly introducing a flag to enable this behavior.

rdar://123513706
---
 clang/include/clang/AST/Availability.h        |   4 +
 clang/lib/AST/Availability.cpp                | 100 ++++++++++--
 .../Serialization/SymbolGraphSerializer.cpp   |  31 ++--
 .../test/ExtractAPI/inherited_availability.m  | 149 ++++++++++++++++++
 4 files changed, 254 insertions(+), 30 deletions(-)
 create mode 100644 clang/test/ExtractAPI/inherited_availability.m

diff --git a/clang/include/clang/AST/Availability.h b/clang/include/clang/AST/Availability.h
index 26ae622e5b4496..60ca1383f0a44e 100644
--- a/clang/include/clang/AST/Availability.h
+++ b/clang/include/clang/AST/Availability.h
@@ -97,6 +97,10 @@ struct AvailabilityInfo {
     return UnconditionallyUnavailable;
   }
 
+  /// Augments the existing information with additional constraints provided by
+  /// \c Other.
+  void mergeWith(AvailabilityInfo Other);
+
   AvailabilityInfo(StringRef Domain, VersionTuple I, VersionTuple D,
                    VersionTuple O, bool U, bool UD, bool UU)
       : Domain(Domain), Introduced(I), Deprecated(D), Obsoleted(O),
diff --git a/clang/lib/AST/Availability.cpp b/clang/lib/AST/Availability.cpp
index 238359a2dedfcf..376a625b41817a 100644
--- a/clang/lib/AST/Availability.cpp
+++ b/clang/lib/AST/Availability.cpp
@@ -16,33 +16,101 @@
 #include "clang/AST/Decl.h"
 #include "clang/Basic/TargetInfo.h"
 
-namespace clang {
+namespace {
+
+struct AvailabilitySet {
+  llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
+  bool UnconditionallyDeprecated = false;
+  bool UnconditionallyUnavailable = false;
 
-AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *Decl) {
-  ASTContext &Context = Decl->getASTContext();
-  StringRef PlatformName = Context.getTargetInfo().getPlatformName();
-  AvailabilityInfo Availability;
+  void insert(clang::AvailabilityInfo &&Availability) {
+    auto *Found = getForPlatform(Availability.Domain);
+    if (Found)
+      Found->mergeWith(std::move(Availability));
+    else
+      Availabilities.emplace_back(std::move(Availability));
+  }
+
+  clang::AvailabilityInfo *getForPlatform(llvm::StringRef Domain) {
+    auto *It = llvm::find_if(Availabilities,
+                             [Domain](const clang::AvailabilityInfo &Info) {
+                               return Domain.compare(Info.Domain) == 0;
+                             });
+    return It == Availabilities.end() ? nullptr : It;
+  }
+};
 
+static void createInfoForDecl(const clang::Decl *Decl,
+                              AvailabilitySet &Availabilities) {
   // Collect availability attributes from all redeclarations.
   for (const auto *RD : Decl->redecls()) {
-    for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
-      if (A->getPlatform()->getName() != PlatformName)
-        continue;
-      Availability = AvailabilityInfo(
+    for (const auto *A : RD->specific_attrs<clang::AvailabilityAttr>()) {
+      Availabilities.insert(clang::AvailabilityInfo(
           A->getPlatform()->getName(), A->getIntroduced(), A->getDeprecated(),
-          A->getObsoleted(), A->getUnavailable(), false, false);
-      break;
+          A->getObsoleted(), A->getUnavailable(), false, false));
     }
 
-    if (const auto *A = RD->getAttr<UnavailableAttr>())
+    if (const auto *A = RD->getAttr<clang::UnavailableAttr>())
       if (!A->isImplicit())
-        Availability.UnconditionallyUnavailable = true;
+        Availabilities.UnconditionallyUnavailable = true;
 
-    if (const auto *A = RD->getAttr<DeprecatedAttr>())
+    if (const auto *A = RD->getAttr<clang::DeprecatedAttr>())
       if (!A->isImplicit())
-        Availability.UnconditionallyDeprecated = true;
+        Availabilities.UnconditionallyDeprecated = true;
+  }
+}
+
+} // namespace
+
+namespace clang {
+
+void AvailabilityInfo::mergeWith(AvailabilityInfo Other) {
+  if (isDefault() && Other.isDefault())
+    return;
+
+  if (Domain.empty())
+    Domain = Other.Domain;
+
+  UnconditionallyUnavailable |= Other.UnconditionallyUnavailable;
+  UnconditionallyDeprecated |= Other.UnconditionallyDeprecated;
+  Unavailable |= Other.Unavailable;
+
+  Introduced = std::max(Introduced, Other.Introduced);
+
+  // Default VersionTuple is 0.0.0 so if both are non default let's pick the
+  // smallest version number, otherwise select the one that is non-zero if there
+  // is one.
+  if (!Deprecated.empty() && !Other.Deprecated.empty())
+    Deprecated = std::min(Deprecated, Other.Deprecated);
+  else
+    Deprecated = std::max(Deprecated, Other.Deprecated);
+
+  if (!Obsoleted.empty() && !Other.Obsoleted.empty())
+    Obsoleted = std::min(Obsoleted, Other.Obsoleted);
+  else
+    Obsoleted = std::max(Obsoleted, Other.Obsoleted);
+}
+
+AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *D) {
+  AvailabilitySet Availabilities;
+  createInfoForDecl(D, Availabilities);
+  // Traverse
+  for (const auto *Ctx = llvm::cast_or_null<Decl>(D->getDeclContext()); Ctx;
+       Ctx = llvm::cast_or_null<Decl>(Ctx->getDeclContext()))
+    createInfoForDecl(Ctx, Availabilities);
+
+  if (auto *Avail = Availabilities.getForPlatform(
+          D->getASTContext().getTargetInfo().getPlatformName())) {
+    Avail->UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated;
+    Avail->UnconditionallyUnavailable =
+        Availabilities.UnconditionallyUnavailable;
+    return std::move(*Avail);
   }
-  return Availability;
+
+  AvailabilityInfo Avail;
+  Avail.UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated;
+  Avail.UnconditionallyUnavailable = Availabilities.UnconditionallyUnavailable;
+  return Avail;
 }
 
 } // namespace clang
diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
index 6e56ee5b573f66..84ed5467dd2fb9 100644
--- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
+++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
@@ -171,22 +171,25 @@ std::optional<Array> serializeAvailability(const AvailabilityInfo &Avail) {
     UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true;
     AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated));
   }
-  Object Availability;
-
-  Availability["domain"] = Avail.Domain;
-
-  if (Avail.isUnavailable()) {
-    Availability["isUnconditionallyUnavailable"] = true;
-  } else {
-    serializeObject(Availability, "introduced",
-                    serializeSemanticVersion(Avail.Introduced));
-    serializeObject(Availability, "deprecated",
-                    serializeSemanticVersion(Avail.Deprecated));
-    serializeObject(Availability, "obsoleted",
-                    serializeSemanticVersion(Avail.Obsoleted));
+
+  if (Avail.Domain.str() != "") {
+    Object Availability;
+    Availability["domain"] = Avail.Domain;
+
+    if (Avail.isUnavailable()) {
+      Availability["isUnconditionallyUnavailable"] = true;
+    } else {
+      serializeObject(Availability, "introduced",
+                      serializeSemanticVersion(Avail.Introduced));
+      serializeObject(Availability, "deprecated",
+                      serializeSemanticVersion(Avail.Deprecated));
+      serializeObject(Availability, "obsoleted",
+                      serializeSemanticVersion(Avail.Obsoleted));
+    }
+
+    AvailabilityArray.emplace_back(std::move(Availability));
   }
 
-  AvailabilityArray.emplace_back(std::move(Availability));
   return AvailabilityArray;
 }
 
diff --git a/clang/test/ExtractAPI/inherited_availability.m b/clang/test/ExtractAPI/inherited_availability.m
new file mode 100644
index 00000000000000..6b62e58b022ae5
--- /dev/null
+++ b/clang/test/ExtractAPI/inherited_availability.m
@@ -0,0 +1,149 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -extract-api --pretty-sgf --emit-sgf-symbol-labels-for-testing -triple arm64-apple-macosx \
+// RUN:   -x objective-c-header %s -o %t/output.symbols.json -verify
+
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix A
+__attribute__((availability(macos, introduced=9.0, deprecated=12.0, obsoleted=20.0)))
+ at interface A
+// A-LABEL: "!testLabel": "c:objc(cs)A"
+// A:      "availability": [
+// A-NEXT:   {
+// A-NEXT:     "deprecated": {
+// A-NEXT:       "major": 12,
+// A-NEXT:       "minor": 0,
+// A-NEXT:       "patch": 0
+// A-NEXT:     }
+// A-NEXT:     "domain": "macos"
+// A-NEXT:     "introduced": {
+// A-NEXT:       "major": 9,
+// A-NEXT:       "minor": 0,
+// A-NEXT:       "patch": 0
+// A-NEXT:     }
+// A-NEXT:     "obsoleted": {
+// A-NEXT:       "major": 20,
+// A-NEXT:       "minor": 0,
+// A-NEXT:       "patch": 0
+// A-NEXT:     }
+// A-NEXT:   }
+// A-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CP
+ at property(class) int CP;
+// CP-LABEL: "!testLabel": "c:objc(cs)A(cpy)CP"
+// CP:      "availability": [
+// CP-NEXT:   {
+// CP-NEXT:     "deprecated": {
+// CP-NEXT:       "major": 12,
+// CP-NEXT:       "minor": 0,
+// CP-NEXT:       "patch": 0
+// CP-NEXT:     }
+// CP-NEXT:     "domain": "macos"
+// CP-NEXT:     "introduced": {
+// CP-NEXT:       "major": 9,
+// CP-NEXT:       "minor": 0,
+// CP-NEXT:       "patch": 0
+// CP-NEXT:     }
+// CP-NEXT:     "obsoleted": {
+// CP-NEXT:       "major": 20,
+// CP-NEXT:       "minor": 0,
+// CP-NEXT:       "patch": 0
+// CP-NEXT:     }
+// CP-NEXT:   }
+// CP-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix IP
+ at property int IP;
+// IP-LABEL: "!testLabel": "c:objc(cs)A(py)IP"
+// IP:      "availability": [
+// IP-NEXT:   {
+// IP-NEXT:     "deprecated": {
+// IP-NEXT:       "major": 12,
+// IP-NEXT:       "minor": 0,
+// IP-NEXT:       "patch": 0
+// IP-NEXT:     }
+// IP-NEXT:     "domain": "macos"
+// IP-NEXT:     "introduced": {
+// IP-NEXT:       "major": 9,
+// IP-NEXT:       "minor": 0,
+// IP-NEXT:       "patch": 0
+// IP-NEXT:     }
+// IP-NEXT:     "obsoleted": {
+// IP-NEXT:       "major": 20,
+// IP-NEXT:       "minor": 0,
+// IP-NEXT:       "patch": 0
+// IP-NEXT:     }
+// IP-NEXT:   }
+// IP-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix MR
+ at property int moreRestrictive __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0)));
+// MR-LABEL: "!testLabel": "c:objc(cs)A(py)moreRestrictive"
+// MR:      "availability": [
+// MR-NEXT:   {
+// MR-NEXT:     "deprecated": {
+// MR-NEXT:       "major": 11,
+// MR-NEXT:       "minor": 0,
+// MR-NEXT:       "patch": 0
+// MR-NEXT:     }
+// MR-NEXT:     "domain": "macos"
+// MR-NEXT:     "introduced": {
+// MR-NEXT:       "major": 10,
+// MR-NEXT:       "minor": 0,
+// MR-NEXT:       "patch": 0
+// MR-NEXT:     }
+// MR-NEXT:     "obsoleted": {
+// MR-NEXT:       "major": 19,
+// MR-NEXT:       "minor": 0,
+// MR-NEXT:       "patch": 0
+// MR-NEXT:     }
+// MR-NEXT:   }
+// MR-NEXT: ]
+
+ at end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix B
+__attribute__((deprecated("B is deprecated")))
+ at interface B
+// B-LABEL: "!testLabel": "c:objc(cs)B"
+// B:      "availability": [
+// B-NEXT:   {
+// B-NEXT:     "domain": "*"
+// B-NEXT:     "isUnconditionallyDeprecated": true
+// B-NEXT:   }
+// B-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix BIP
+ at property int BIP;
+// BIP-LABEL: "!testLabel": "c:objc(cs)B(py)BIP"
+// BIP:      "availability": [
+// BIP-NEXT:   {
+// BIP-NEXT:     "domain": "*"
+// BIP-NEXT:     "isUnconditionallyDeprecated": true
+// BIP-NEXT:   }
+// BIP-NEXT: ]
+ at end
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix C
+__attribute__((availability(macos, unavailable)))
+ at interface C
+// C-LABEL: "!testLabel": "c:objc(cs)C"
+// C:      "availability": [
+// C-NEXT:   {
+// C-NEXT:     "domain": "macos"
+// C-NEXT:     "isUnconditionallyUnavailable": true
+// C-NEXT:   }
+// C-NEXT: ]
+
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CIP
+ at property int CIP;
+// CIP-LABEL: "!testLabel": "c:objc(cs)C(py)CIP"
+// CIP:      "availability": [
+// CIP-NEXT:   {
+// CIP-NEXT:     "domain": "macos"
+// CIP-NEXT:     "isUnconditionallyUnavailable": true
+// CIP-NEXT:   }
+// CIP-NEXT: ]
+ at end
+
+// expected-no-diagnostics

>From 461c9a684e8b0eaa20b0260b40040b8caf886d03 Mon Sep 17 00:00:00 2001
From: Daniel Grumberg <dgrumberg at apple.com>
Date: Wed, 14 Aug 2024 10:45:42 +0100
Subject: [PATCH 2/5] Check correct availability in SGF when parent symbol has
 no specified availability

---
 .../test/ExtractAPI/inherited_availability.m  | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/clang/test/ExtractAPI/inherited_availability.m b/clang/test/ExtractAPI/inherited_availability.m
index 6b62e58b022ae5..c24e7fa8e208f0 100644
--- a/clang/test/ExtractAPI/inherited_availability.m
+++ b/clang/test/ExtractAPI/inherited_availability.m
@@ -146,4 +146,30 @@ @interface C
 // CIP-NEXT: ]
 @end
 
+ at interface D
+// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix DIP
+ at property int DIP __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0)));
+// DIP-LABEL: "!testLabel": "c:objc(cs)D(py)DIP"
+// DIP:      "availability": [
+// DIP-NEXT:   {
+// DIP-NEXT:     "deprecated": {
+// DIP-NEXT:       "major": 11,
+// DIP-NEXT:       "minor": 0,
+// DIP-NEXT:       "patch": 0
+// DIP-NEXT:     }
+// DIP-NEXT:     "domain": "macos"
+// DIP-NEXT:     "introduced": {
+// DIP-NEXT:       "major": 10,
+// DIP-NEXT:       "minor": 0,
+// DIP-NEXT:       "patch": 0
+// DIP-NEXT:     }
+// DIP-NEXT:     "obsoleted": {
+// DIP-NEXT:       "major": 19,
+// DIP-NEXT:       "minor": 0,
+// DIP-NEXT:       "patch": 0
+// DIP-NEXT:     }
+// DIP-NEXT:   }
+// DIP-NEXT: ]
+ at end
+
 // expected-no-diagnostics

>From dafea6d9560ab2665805a2b32641e3eb38829501 Mon Sep 17 00:00:00 2001
From: Daniel Grumberg <dgrumberg at apple.com>
Date: Wed, 14 Aug 2024 10:51:02 +0100
Subject: [PATCH 3/5] Add docstring explaining purpose of AvailabilitySet

---
 clang/lib/AST/Availability.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/AST/Availability.cpp b/clang/lib/AST/Availability.cpp
index 376a625b41817a..760c3acf5993f8 100644
--- a/clang/lib/AST/Availability.cpp
+++ b/clang/lib/AST/Availability.cpp
@@ -18,6 +18,7 @@
 
 namespace {
 
+/// Represents the availability of a symbol across platforms.
 struct AvailabilitySet {
   llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
   bool UnconditionallyDeprecated = false;

>From d322efbeaf3c96a3eedd2d9b59767755acd10333 Mon Sep 17 00:00:00 2001
From: Daniel Grumberg <dgrumberg at apple.com>
Date: Wed, 14 Aug 2024 10:51:22 +0100
Subject: [PATCH 4/5] Force AvailabilitySet insertion to go through insert
 method

---
 clang/lib/AST/Availability.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/AST/Availability.cpp b/clang/lib/AST/Availability.cpp
index 760c3acf5993f8..54b45167713a5c 100644
--- a/clang/lib/AST/Availability.cpp
+++ b/clang/lib/AST/Availability.cpp
@@ -20,7 +20,6 @@ namespace {
 
 /// Represents the availability of a symbol across platforms.
 struct AvailabilitySet {
-  llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
   bool UnconditionallyDeprecated = false;
   bool UnconditionallyUnavailable = false;
 
@@ -39,6 +38,9 @@ struct AvailabilitySet {
                              });
     return It == Availabilities.end() ? nullptr : It;
   }
+
+private:
+  llvm::SmallVector<clang::AvailabilityInfo> Availabilities;
 };
 
 static void createInfoForDecl(const clang::Decl *Decl,

>From 801f1193dfac1f4725ee94b63928ad53ede7bd1b Mon Sep 17 00:00:00 2001
From: Daniel Grumberg <dgrumberg at apple.com>
Date: Wed, 14 Aug 2024 11:04:08 +0100
Subject: [PATCH 5/5] Simplify implementation of
 `AvailabityInfo::createFromDecl`

---
 clang/lib/AST/Availability.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/lib/AST/Availability.cpp b/clang/lib/AST/Availability.cpp
index 54b45167713a5c..cf040fc727d11f 100644
--- a/clang/lib/AST/Availability.cpp
+++ b/clang/lib/AST/Availability.cpp
@@ -96,9 +96,9 @@ void AvailabilityInfo::mergeWith(AvailabilityInfo Other) {
 
 AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *D) {
   AvailabilitySet Availabilities;
-  createInfoForDecl(D, Availabilities);
-  // Traverse
-  for (const auto *Ctx = llvm::cast_or_null<Decl>(D->getDeclContext()); Ctx;
+  // Walk DeclContexts upwards starting from D to find the combined availability
+  // of the symbol.
+  for (const auto *Ctx = D; Ctx;
        Ctx = llvm::cast_or_null<Decl>(Ctx->getDeclContext()))
     createInfoForDecl(Ctx, Availabilities);
 



More information about the cfe-commits mailing list