[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