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 <dgrumb...@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))) +@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 +@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 +@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 +@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: ] + +@end + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix B +__attribute__((deprecated("B is deprecated"))) +@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 +@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: ] +@end + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix C +__attribute__((availability(macos, unavailable))) +@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 +@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: ] +@end + +// expected-no-diagnostics >From 461c9a684e8b0eaa20b0260b40040b8caf886d03 Mon Sep 17 00:00:00 2001 From: Daniel Grumberg <dgrumb...@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 +@interface D +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix DIP +@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: ] +@end + // expected-no-diagnostics >From dafea6d9560ab2665805a2b32641e3eb38829501 Mon Sep 17 00:00:00 2001 From: Daniel Grumberg <dgrumb...@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 <dgrumb...@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 <dgrumb...@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); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits