From ce0e42e2afd0ee97e7909102b36bc60a8d66d511 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Fri, 19 Sep 2025 09:38:01 -0700 Subject: [PATCH 1/8] [domain availability] Implement Sema support for permanently enabled Unified Feature Flags This implements Sema support for the flag added in 1afc96bfc824374f443ed623e942a423f7f151cc. rdar://157602517 --- clang/lib/Sema/SemaFeatureAvailability.cpp | 6 ++++++ clang/test/CodeGen/feature-availability.c | 7 +++++++ clang/test/CodeGenObjC/feature-availability.m | 8 +++++++- clang/test/Sema/feature-availability.c | 4 ++-- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index ac92b309379f3..269e5cb2d7e35 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -165,6 +165,12 @@ void DiagnoseUnguardedFeatureAvailability::diagnoseDeclFeatureAvailability( const NamedDecl *D, SourceLocation Loc) { for (auto *Attr : D->specific_attrs()) { std::string FeatureUse = Attr->getDomain().str(); + // Skip checking if the feature is always enabled. + if (!Attr->getUnavailable() && + SemaRef.Context.getFeatureAvailInfo(FeatureUse).Kind == + FeatureAvailKind::AlwaysAvailable) + continue; + if (!isFeatureUseGuarded(Attr)) SemaRef.Diag(Loc, diag::err_unguarded_feature) << D << FeatureUse << Attr->getUnavailable(); diff --git a/clang/test/CodeGen/feature-availability.c b/clang/test/CodeGen/feature-availability.c index 94a72e4f67e34..2956a61b5160f 100644 --- a/clang/test/CodeGen/feature-availability.c +++ b/clang/test/CodeGen/feature-availability.c @@ -1,11 +1,14 @@ // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -ffeature-availability=feature1:on -ffeature-availability=feature2:off -emit-llvm -o - %s | FileCheck %s // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -emit-llvm -o - -DUSE_DOMAIN %s | FileCheck --check-prefixes=CHECK,DOMAIN %s +// RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -emit-llvm -o - -DUSE_DOMAIN -DALWAYS_ENABLED %s | FileCheck --check-prefixes=CHECK,DOMAIN %s // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -ffeature-availability=feature1:on -ffeature-availability=feature2:off -emit-pch -o %t %s // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -ffeature-availability=feature1:on -ffeature-availability=feature2:off -include-pch %t -emit-llvm -o - %s | FileCheck %s // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -emit-pch -o %t -DUSE_DOMAIN %s // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -include-pch %t -emit-llvm -o - -DUSE_DOMAIN %s | FileCheck --check-prefixes=CHECK,DOMAIN %s +// RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -emit-pch -o %t -DUSE_DOMAIN -DALWAYS_ENABLED %s +// RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -include-pch %t -emit-llvm -o - -DUSE_DOMAIN -DALWAYS_ENABLED %s | FileCheck --check-prefixes=CHECK,DOMAIN %s // CHECK: %[[STRUCT_S0:.*]] = type { i32 } // CHECK: @g0 = external global i32, align 4 @@ -22,7 +25,11 @@ #ifdef USE_DOMAIN // DOMAIN: @g3 = extern_weak global i32, align 4 +#ifdef ALWAYS_ENABLED +CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(feature1); +#else CLANG_ENABLED_AVAILABILITY_DOMAIN(feature1); +#endif CLANG_DISABLED_AVAILABILITY_DOMAIN(feature2); #endif diff --git a/clang/test/CodeGenObjC/feature-availability.m b/clang/test/CodeGenObjC/feature-availability.m index 67ad95dcf260f..13b2a43b9d756 100644 --- a/clang/test/CodeGenObjC/feature-availability.m +++ b/clang/test/CodeGenObjC/feature-availability.m @@ -1,15 +1,21 @@ // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -ffeature-availability=feature1:on -ffeature-availability=feature2:off -ffeature-availability=feature3:on -emit-llvm -o - %s | FileCheck %s // RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -emit-llvm -o - -DUSE_DOMAIN %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64-apple-macosx -fblocks -emit-llvm -o - -DUSE_DOMAIN -DALWAYS_ENABLED %s | FileCheck %s #include #define AVAIL 0 #ifdef USE_DOMAIN +#ifdef ALWAYS_ENABLED +CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(feature1); +CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(feature3); +#else CLANG_ENABLED_AVAILABILITY_DOMAIN(feature1); -CLANG_DISABLED_AVAILABILITY_DOMAIN(feature2); CLANG_ENABLED_AVAILABILITY_DOMAIN(feature3); #endif +CLANG_DISABLED_AVAILABILITY_DOMAIN(feature2); +#endif // CHECK: @"OBJC_CLASS_$_C0" = global %struct._class_t { ptr @"OBJC_METACLASS_$_C0", ptr null, ptr @_objc_empty_cache, ptr @_objc_empty_vtable, ptr @"_OBJC_CLASS_RO_$_C0" }, section "__DATA, __objc_data", align 8 // CHECK-NEXT: @"OBJC_METACLASS_$_C0" = global %struct._class_t { ptr @"OBJC_METACLASS_$_C0", ptr @"OBJC_CLASS_$_C0", ptr @_objc_empty_cache, ptr @_objc_empty_vtable, ptr @"_OBJC_METACLASS_RO_$_C0" }, section "__DATA, __objc_data", align 8 diff --git a/clang/test/Sema/feature-availability.c b/clang/test/Sema/feature-availability.c index 22f30777aa4d3..ee58b73969968 100644 --- a/clang/test/Sema/feature-availability.c +++ b/clang/test/Sema/feature-availability.c @@ -258,14 +258,14 @@ void test7(void) { #ifdef USE_DOMAIN void test8(void) { // FIXME: Use of 'func21()' should not be diagnosed because feature5 is always available. - func21(); // expected-error {{cannot use 'func21' because feature 'feature5' is unavailable in this context}} + func21(); func22(); // expected-error {{cannot use 'func22' because feature 'feature5' is available in this context}} if (__builtin_available(domain:feature5)) { func21(); func22(); // expected-error {{cannot use 'func22' because feature 'feature5' is available in this context}} } else { - func21(); // expected-error {{cannot use 'func21' because feature 'feature5' is unavailable in this context}} + func21(); func22(); } } From a5b238082f5386f03958f0c265fec6f6a6182a0e Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Mon, 22 Sep 2025 11:48:59 -0700 Subject: [PATCH 2/8] Add missing check for always-enabled feature Also test always-enabled features. --- clang/lib/Sema/SemaFeatureAvailability.cpp | 12 +++++++-- clang/test/SemaObjC/feature-availability.m | 29 +++++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index 269e5cb2d7e35..4e4344cc30414 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -33,10 +33,18 @@ static bool isFeatureUseGuarded(const DomainAvailabilityAttr *AA, static void diagnoseDeclFeatureAvailability(const NamedDecl *D, SourceLocation Loc, Decl *ContextDecl, Sema &S) { - for (auto *Attr : D->specific_attrs()) + for (auto *Attr : D->specific_attrs()) { + std::string FeatureUse = Attr->getDomain().str(); + // Skip checking if the feature is always enabled. + if (!Attr->getUnavailable() && + S.Context.getFeatureAvailInfo(FeatureUse).Kind == + FeatureAvailKind::AlwaysAvailable) + continue; + if (!isFeatureUseGuarded(Attr, ContextDecl, S.Context)) S.Diag(Loc, diag::err_unguarded_feature) - << D << Attr->getDomain().str() << Attr->getUnavailable(); + << D << FeatureUse << Attr->getUnavailable(); + } } class DiagnoseUnguardedFeatureAvailability diff --git a/clang/test/SemaObjC/feature-availability.m b/clang/test/SemaObjC/feature-availability.m index c14a8df04e008..d2c9195ca3813 100644 --- a/clang/test/SemaObjC/feature-availability.m +++ b/clang/test/SemaObjC/feature-availability.m @@ -1,5 +1,6 @@ -// RUN: %clang_cc1 -fblocks -ffeature-availability=feature1:ON -ffeature-availability=feature2:OFF -fsyntax-only -verify %s -// RUN: %clang_cc1 -fblocks -fsyntax-only -verify -DUSE_DOMAIN %s +// RUN: %clang_cc1 -fblocks -ffeature-availability=feature1:ON -ffeature-availability=feature2:OFF -fsyntax-only -verify=expected,enabled %s +// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected,enabled -DUSE_DOMAIN %s +// RUN: %clang_cc1 -fblocks -fsyntax-only -verify=expected -DUSE_DOMAIN -DALWAYS_ENABLED %s #include @@ -7,7 +8,11 @@ #define UNAVAIL 1 #ifdef USE_DOMAIN +#ifdef ALWAYS_ENABLED +CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(feature1); +#else CLANG_ENABLED_AVAILABILITY_DOMAIN(feature1); +#endif CLANG_DISABLED_AVAILABILITY_DOMAIN(feature2); #endif @@ -19,18 +24,18 @@ @interface C0 { struct S0 ivar0; // expected-error {{cannot use 'S0' because feature 'feature1' is available in this context}} - struct S1 ivar1; // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} + struct S1 ivar1; // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} struct S1 ivar2 __attribute__((availability(domain:feature1, AVAIL))); - struct S1 ivar3 __attribute__((availability(domain:feature1, UNAVAIL))); // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} + struct S1 ivar3 __attribute__((availability(domain:feature1, UNAVAIL))); // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} } @property struct S0 prop0; // expected-error {{cannot use 'S0' because feature 'feature1' is available in this context}} -@property struct S1 prop1; // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} +@property struct S1 prop1; // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} @property struct S1 prop2 __attribute__((availability(domain:feature1, AVAIL))); -@property struct S1 prop3 __attribute__((availability(domain:feature1, UNAVAIL))); // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} +@property struct S1 prop3 __attribute__((availability(domain:feature1, UNAVAIL))); // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} -(struct S0)m0; // expected-error {{cannot use 'S0' because feature 'feature1' is available in this context}} --(struct S1)m1; // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} +-(struct S1)m1; // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} -(struct S1)m2 __attribute__((availability(domain:feature1, AVAIL))); --(struct S1)m3 __attribute__((availability(domain:feature1, UNAVAIL))); // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} +-(struct S1)m3 __attribute__((availability(domain:feature1, UNAVAIL))); // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} @end @class Base0; @@ -59,7 +64,7 @@ @interface NSObject @interface Base7 : NSObject @end -@interface Derived3 : Base7 // expected-error {{cannot use 'Base0' because feature 'feature1' is unavailable in this context}} +@interface Derived3 : Base7 // enabled-error {{cannot use 'Base0' because feature 'feature1' is unavailable in this context}} @end __attribute__((availability(domain:feature1, AVAIL))) // expected-note {{is incompatible with __attribute__((availability(domain:feature1, 0)))}} expected-note 2 {{feature attribute __attribute__((availability(domain:feature1, 0)))}} @@ -116,7 +121,7 @@ @interface Derived1(C1) // expected-error {{cannot merge incompatible feature at @end @protocol P0 -@property struct S1 *p0; // expected-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} +@property struct S1 *p0; // enabled-error {{cannot use 'S1' because feature 'feature1' is unavailable in this context}} @end __attribute__((availability(domain:feature1, AVAIL))) @@ -198,8 +203,8 @@ @implementation Derived9 : Base9 -(void)m4 { // Check that this method doesn't inherit the domain availablity attribute // from the base class method. - func1(); // expected-error {{cannot use 'func1' because feature 'feature1' is unavailable in this context}} + func1(); // enabled-error {{cannot use 'func1' because feature 'feature1' is unavailable in this context}} - [super m4]; // expected-error {{cannot use 'm4' because feature 'feature1' is unavailable in this context}} + [super m4]; // enabled-error {{cannot use 'm4' because feature 'feature1' is unavailable in this context}} } @end From 367ff3026eca286d371f4fdd5d787cf5c63ca9eb Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Mon, 22 Sep 2025 12:12:47 -0700 Subject: [PATCH 3/8] Remove obsolete FIXME --- clang/test/Sema/feature-availability.c | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/test/Sema/feature-availability.c b/clang/test/Sema/feature-availability.c index ee58b73969968..72953cc428621 100644 --- a/clang/test/Sema/feature-availability.c +++ b/clang/test/Sema/feature-availability.c @@ -257,7 +257,6 @@ void test7(void) { #ifdef USE_DOMAIN void test8(void) { - // FIXME: Use of 'func21()' should not be diagnosed because feature5 is always available. func21(); func22(); // expected-error {{cannot use 'func22' because feature 'feature5' is available in this context}} From 8a9b96771da101101d5a56ee30b1177dc87fa408 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Tue, 23 Sep 2025 12:04:24 -0700 Subject: [PATCH 4/8] Diagnose deprecated availability domains rdar://157603372 --- clang/include/clang/AST/ASTContext.h | 1 + .../clang/Basic/DiagnosticSemaKinds.td | 5 +++ clang/include/clang/Sema/Sema.h | 5 +++ clang/lib/AST/ASTContext.cpp | 3 +- clang/lib/Sema/SemaDeclAttr.cpp | 4 +++ clang/lib/Sema/SemaExprObjC.cpp | 5 +++ clang/lib/Sema/SemaFeatureAvailability.cpp | 14 ++++++++ clang/test/Sema/feature-availability.c | 28 +++++++++++++++ clang/test/SemaObjC/feature-availability.m | 34 +++++++++++++++++++ 9 files changed, 98 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 64fbaec234699..1b22649a3b64e 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -884,6 +884,7 @@ class ASTContext : public RefCountedBase { FeatureAvailKind Kind = FeatureAvailKind::None; clang::Decl *Decl = nullptr; ImplicitCastExpr *Call = nullptr; + bool IsDeprecated = false; bool isInvalid() const { return Kind == FeatureAvailKind::None; } }; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index b38d7704e3450..a5471e77119a7 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4121,6 +4121,11 @@ def err_features_invalid_for_decl : Error< "feature attributes cannot be applied to %0">; def err_features_invalid_name : Error< "cannot find definition of feature attribute '%0'">; +def warn_deprecated_availability_domain : Warning< + "availability domain '%0' is deprecated">, InGroup>; +def warn_permanently_available_domain : Warning< + "%select{unnecessary check for '%0'; this expression will always evaluate to true|attribute has no effect because '%0' is always available}1">, + InGroup>; def err_features_invalid__arg : Error< "invalid argument %0: must evaluate to 0 or 1">; def err_feature_invalid_for_decl : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 410566bf9e6aa..6b13b7d981481 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2670,6 +2670,11 @@ class Sema final : public SemaBase { void DiagnoseFeatureAvailabilityOfDecl(NamedDecl *D, ArrayRef Locs); + void diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, + SourceLocation AtLoc, + SourceLocation DomainLoc, + bool IsDecl); + /// Retrieve the current function, if any, that should be analyzed for /// potential availability violations. sema::FunctionScopeInfo *getCurFunctionAvailabilityContext(); diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 8e1f68739d1cd..fae310032e9e4 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -1018,7 +1018,8 @@ ASTContext::getFeatureAvailInfo(Decl *D) const { llvm_unreachable("invalid feature kind"); } - ASTContext::AvailabilityDomainInfo Info{Kind, D, nullptr}; + ASTContext::AvailabilityDomainInfo Info{Kind, D, nullptr, + D->hasAttr()}; if (Kind == FeatureAvailKind::Dynamic) { Expr *FnExpr = Init->getInit(1); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 4c6b6a2b48a72..1c9d960881825 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2485,6 +2485,10 @@ static void handleFeatureAvailabilityAttr(Sema &S, Decl *D, return; } + S.diagnoseDeprecatedAvailabilityDomain(II->getName(), AL.getLoc(), + AL.getArgAsIdent(0)->getLoc(), + /*IsDecl=*/true); + int IsUnavailable = AL.getArgAsExpr(1)->getIntegerConstantExpr(S.Context)->getExtValue(); diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp index cf1e4945aaf08..1732caf652897 100644 --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/clang/lib/Sema/SemaExprObjC.cpp @@ -5162,6 +5162,11 @@ ExprResult SemaObjC::ActOnObjCAvailabilityCheckExpr( if (FunctionScopeInfo *Info = SemaRef.getCurFunctionAvailabilityContext()) Info->HasPotentialFeatureAvailabilityViolations = true; auto Spec = AvailSpecs.front(); + + SemaRef.diagnoseDeprecatedAvailabilityDomain(Spec.getDomainName(), AtLoc, + Spec.getBeginLoc(), + /*IsDecl=*/false); + return ObjCAvailabilityCheckExpr::CreateAvailabilityFeatureCheck( AtLoc, RParen, Context.BoolTy, Spec.getDomainName(), Context); } diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index 4e4344cc30414..e3af281bb917b 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -240,3 +240,17 @@ void Sema::DiagnoseFeatureAvailabilityOfDecl(NamedDecl *D, Decl *Ctx = cast(getCurLexicalContext()); diagnoseDeclFeatureAvailability(D, Locs.front(), Ctx, *this); } + +void Sema::diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, + SourceLocation AtLoc, + SourceLocation DomainLoc, + bool IsDecl) { + ASTContext::AvailabilityDomainInfo Info = + Context.getFeatureAvailInfo(DomainName); + if (Info.IsDeprecated) { + Diag(DomainLoc, diag::warn_deprecated_availability_domain) << DomainName; + if (Info.Kind == FeatureAvailKind::AlwaysAvailable) + Diag(AtLoc, diag::warn_permanently_available_domain) + << DomainName << IsDecl; + } +} diff --git a/clang/test/Sema/feature-availability.c b/clang/test/Sema/feature-availability.c index 72953cc428621..1c08f3d3a7bbb 100644 --- a/clang/test/Sema/feature-availability.c +++ b/clang/test/Sema/feature-availability.c @@ -14,6 +14,10 @@ CLANG_DISABLED_AVAILABILITY_DOMAIN(feature2); CLANG_ENABLED_AVAILABILITY_DOMAIN(feature3); CLANG_DYNAMIC_AVAILABILITY_DOMAIN(feature4, pred1); CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(feature5); +__attribute__((deprecated)) +CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(deprecated_feature1); +__attribute__((deprecated)) +CLANG_ENABLED_AVAILABILITY_DOMAIN(deprecated_feature2); #endif #pragma clang attribute push (__attribute__((availability(domain:feature1, AVAIL))), apply_to=any(function)) @@ -37,6 +41,14 @@ __attribute__((availability(domain:feature4, AVAIL))) int g4; __attribute__((availability(domain:feature4, UNAVAIL))) int g5; __attribute__((availability(domain:feature5, AVAIL))) void func21(void); __attribute__((availability(domain:feature5, UNAVAIL))) void func22(void); +__attribute__((availability(domain:deprecated_feature1, AVAIL))) void func23(void); +// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} +__attribute__((availability(domain:deprecated_feature1, UNAVAIL))) void func24(void); +// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} +__attribute__((availability(domain:deprecated_feature2, AVAIL))) void func25(void); +// expected-warning@-1 {{availability domain 'deprecated_feature2' is deprecated}} #endif void test_unreachable_code(void) { @@ -268,4 +280,20 @@ void test8(void) { func22(); } } + +void test_deprecated_feature(void) { + if (__builtin_available(domain:deprecated_feature1)) + // expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} + // expected-warning@-2 {{unnecessary check for 'deprecated_feature1'; this expression will always evaluate to true}} + ; + + if (!__builtin_available(domain:deprecated_feature1)) + // expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} + // expected-warning@-2 {{unnecessary check for 'deprecated_feature1'; this expression will always evaluate to true}} + ; + + if (__builtin_available(domain:deprecated_feature2)) + // expected-warning@-1 {{availability domain 'deprecated_feature2' is deprecated}} + ; +} #endif diff --git a/clang/test/SemaObjC/feature-availability.m b/clang/test/SemaObjC/feature-availability.m index d2c9195ca3813..05fa09c21b024 100644 --- a/clang/test/SemaObjC/feature-availability.m +++ b/clang/test/SemaObjC/feature-availability.m @@ -14,6 +14,10 @@ CLANG_ENABLED_AVAILABILITY_DOMAIN(feature1); #endif CLANG_DISABLED_AVAILABILITY_DOMAIN(feature2); +__attribute__((deprecated)) +CLANG_ALWAYS_ENABLED_AVAILABILITY_DOMAIN(deprecated_feature1); +__attribute__((deprecated)) +CLANG_ENABLED_AVAILABILITY_DOMAIN(deprecated_feature2); #endif __attribute__((availability(domain:feature1, AVAIL))) int func1(void); @@ -208,3 +212,33 @@ -(void)m4 { [super m4]; // enabled-error {{cannot use 'm4' because feature 'feature1' is unavailable in this context}} } @end + +#ifdef USE_DOMAIN +__attribute__((availability(domain:deprecated_feature1, AVAIL))) +// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} +@interface DeprecatedC1 +@end + +__attribute__((availability(domain:deprecated_feature1, UNAVAIL))) +// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} +@interface DeprecatedC2 +@end + +__attribute__((availability(domain:deprecated_feature2, AVAIL))) +// expected-warning@-1 {{availability domain 'deprecated_feature2' is deprecated}} +@interface DeprecatedC3 +@end + +void test_deprecated1(void) { + if (@available(domain:deprecated_feature1)) + // expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} + // expected-warning@-2 {{unnecessary check for 'deprecated_feature1'; this expression will always evaluate to true}} + ; + + if (@available(domain:deprecated_feature2)) + // expected-warning@-1 {{availability domain 'deprecated_feature2' is deprecated}} + ; +} +#endif From b4250d149ce0a76a3aac528ed32f237a55f830f3 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Mon, 29 Sep 2025 14:29:45 -0700 Subject: [PATCH 5/8] Fix the warning for unavailable domains and emit fixits --- clang/include/clang/Basic/DiagnosticGroups.td | 2 + .../clang/Basic/DiagnosticSemaKinds.td | 9 +- clang/include/clang/Sema/Sema.h | 5 +- clang/lib/Sema/SemaDeclAttr.cpp | 8 +- clang/lib/Sema/SemaExprObjC.cpp | 3 +- clang/lib/Sema/SemaFeatureAvailability.cpp | 92 ++++++++++++++++++- clang/test/Sema/feature-availability.c | 79 ++++++++++++++-- clang/test/SemaObjC/feature-availability.m | 2 +- 8 files changed, 175 insertions(+), 25 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 15885907d39e3..d98c6ef380a41 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -287,6 +287,8 @@ def Deprecated : DiagGroup<"deprecated", [DeprecatedAnonEnumEnumConversion, ]>, DiagCategory<"Deprecations">; +def PermanentlyAvailableDomain : DiagGroup<"permanently-available-domain">; + def CXX20Designator : DiagGroup<"c++20-designator">; // Allow -Wno-c99-designator to be used to turn off all warnings on valid C99 // designators (including the warning controlled by -Wc++20-designator). diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a5471e77119a7..2fe8e2b96c55d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4123,9 +4123,12 @@ def err_features_invalid_name : Error< "cannot find definition of feature attribute '%0'">; def warn_deprecated_availability_domain : Warning< "availability domain '%0' is deprecated">, InGroup>; -def warn_permanently_available_domain : Warning< - "%select{unnecessary check for '%0'; this expression will always evaluate to true|attribute has no effect because '%0' is always available}1">, - InGroup>; +def warn_permanently_available_domain_expr : Warning< + "unnecessary check for '%0'; this expression will always evaluate to true">, + InGroup; +def warn_permanently_available_domain_decl : Warning< + "%select{attribute has no effect because '%0' is always available|declaration is permanently unavailable because '%0' is always available}1">, + InGroup; def err_features_invalid__arg : Error< "invalid argument %0: must evaluate to 0 or 1">; def err_feature_invalid_for_decl : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6b13b7d981481..663a03f49bbe8 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2671,9 +2671,10 @@ class Sema final : public SemaBase { ArrayRef Locs); void diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, - SourceLocation AtLoc, + SourceLocation AvailLoc, SourceLocation DomainLoc, - bool IsDecl); + bool IsUnavailable = false, + const ParsedAttr *PA = nullptr); /// Retrieve the current function, if any, that should be analyzed for /// potential availability violations. diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 1c9d960881825..c8285db69c4bb 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2485,13 +2485,13 @@ static void handleFeatureAvailabilityAttr(Sema &S, Decl *D, return; } - S.diagnoseDeprecatedAvailabilityDomain(II->getName(), AL.getLoc(), - AL.getArgAsIdent(0)->getLoc(), - /*IsDecl=*/true); - int IsUnavailable = AL.getArgAsExpr(1)->getIntegerConstantExpr(S.Context)->getExtValue(); + S.diagnoseDeprecatedAvailabilityDomain(II->getName(), AL.getLoc(), + AL.getArgAsIdent(0)->getLoc(), + IsUnavailable, &AL); + if (IsUnavailable != 0 && IsUnavailable != 1) { S.Diag(AL.getArgAsExpr(1)->getExprLoc(), diag::err_features_invalid__arg) << IsUnavailable; diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp index 1732caf652897..f0e6855aedf33 100644 --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/clang/lib/Sema/SemaExprObjC.cpp @@ -5164,8 +5164,7 @@ ExprResult SemaObjC::ActOnObjCAvailabilityCheckExpr( auto Spec = AvailSpecs.front(); SemaRef.diagnoseDeprecatedAvailabilityDomain(Spec.getDomainName(), AtLoc, - Spec.getBeginLoc(), - /*IsDecl=*/false); + Spec.getBeginLoc()); return ObjCAvailabilityCheckExpr::CreateAvailabilityFeatureCheck( AtLoc, RParen, Context.BoolTy, Spec.getDomainName(), Context); diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index e3af281bb917b..5a77cfebb47e4 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -13,6 +13,9 @@ #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/Sema.h" @@ -242,15 +245,94 @@ void Sema::DiagnoseFeatureAvailabilityOfDecl(NamedDecl *D, } void Sema::diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, - SourceLocation AtLoc, + SourceLocation AvailLoc, SourceLocation DomainLoc, - bool IsDecl) { + bool IsUnavailable, + const ParsedAttr *PA) { + auto CreateFixIt = [&]() { + if (auto *MacroII = PA->getMacroIdentifier()) { + StringRef MacroName = MacroII->getName(); + if (MacroName != "FEATURE_AVAILABLE" && + MacroName != "FEATURE_UNAVAILABLE") + return FixItHint{}; + + // FIXME: Check the file that defines the macros too. + FileID FID = SourceMgr.getFileID(AvailLoc); + const SrcMgr::ExpansionInfo *EI = + &SourceMgr.getSLocEntry(FID).getExpansion(); + if (IsUnavailable) + return FixItHint::CreateReplacement(EI->getExpansionLocRange(), + "__attribute__((unavailable))"); + return FixItHint::CreateRemoval(EI->getExpansionLocRange()); + } + + if (PA->getSyntax() != AttributeCommonInfo::Syntax::AS_GNU) + return FixItHint{}; + + SourceRange AttrRange = PA->getRange(); + + // Replace the availability attribute with "unavailable". + if (IsUnavailable) + return FixItHint::CreateReplacement(AttrRange, "unavailable"); + + // Remove the availability attribute. + + // If there is a leading comma, there's another operand that precedes the + // availability attribute. In that case, remove the availability attribute + // and the comma. + Token PrevTok = *Lexer::findPreviousToken(AttrRange.getBegin(), SourceMgr, + getLangOpts(), false); + if (PrevTok.is(tok::comma)) + return FixItHint::CreateRemoval( + SourceRange(PrevTok.getLocation(), AttrRange.getEnd())); + + // If there is a trailing comma, there's another operand that follows the + // availability attribute. In that case, remove the availability attribute + // and the comma. + Token NextTok = *Lexer::findNextToken(AttrRange.getEnd(), SourceMgr, + getLangOpts(), false); + if (NextTok.is(tok::comma)) + return FixItHint::CreateRemoval( + SourceRange(AttrRange.getBegin(), NextTok.getLocation())); + + // If no leading or trailing commas are found, the availability attribute is + // the only operand. Remove the entire attribute construct. + + // Look for '__attribute'. + for (int i = 0; i < 2; ++i) + PrevTok = *Lexer::findPreviousToken(PrevTok.getLocation(), SourceMgr, + getLangOpts(), false); + if (!PrevTok.is(tok::raw_identifier) || + PrevTok.getRawIdentifier() != "__attribute__") + return FixItHint{}; + + // Look for the closing ')'. + NextTok = *Lexer::findNextToken(NextTok.getLocation(), SourceMgr, + getLangOpts(), false); + if (!NextTok.is(tok::r_paren)) + return FixItHint{}; + + return FixItHint::CreateRemoval( + SourceRange(PrevTok.getLocation(), NextTok.getLocation())); + }; + ASTContext::AvailabilityDomainInfo Info = Context.getFeatureAvailInfo(DomainName); + if (Info.IsDeprecated) { Diag(DomainLoc, diag::warn_deprecated_availability_domain) << DomainName; - if (Info.Kind == FeatureAvailKind::AlwaysAvailable) - Diag(AtLoc, diag::warn_permanently_available_domain) - << DomainName << IsDecl; + if (Info.Kind == FeatureAvailKind::AlwaysAvailable) { + if (PA) { + auto FixitDiag = + Diag(AvailLoc, diag::warn_permanently_available_domain_decl) + << DomainName << IsUnavailable; + + FixItHint Hint = CreateFixIt(); + if (!Hint.isNull()) + FixitDiag << Hint; + } else + Diag(AvailLoc, diag::warn_permanently_available_domain_expr) + << DomainName; + } } } diff --git a/clang/test/Sema/feature-availability.c b/clang/test/Sema/feature-availability.c index 1c08f3d3a7bbb..07df1aeb70062 100644 --- a/clang/test/Sema/feature-availability.c +++ b/clang/test/Sema/feature-availability.c @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -triple arm64-apple-macosx15 -fblocks -ffeature-availability=feature1:1 -ffeature-availability=feature2:0 -ffeature-availability=feature3:on -fsyntax-only -Wunreachable-code -verify %s // RUN: %clang_cc1 -triple arm64-apple-macosx15 -fblocks -fsyntax-only -Wunreachable-code -verify -DUSE_DOMAIN %s +// RUN: not %clang_cc1 -triple arm64-apple-macosx15 -fblocks -fsyntax-only -Wunreachable-code -DUSE_DOMAIN -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s #include @@ -7,6 +8,15 @@ #define UNAVAIL 1 #define INVALID 2 +#define FEATURE_AVAILABLE(domain_name) \ + __attribute__((availability(domain : domain_name, AVAIL))) + +#define MY_FEATURE_AVAILABLE(domain_name) \ + __attribute__((availability(domain : domain_name, AVAIL))) + +#define FEATURE_UNAVAILABLE(domain_name) \ + __attribute__((availability(domain : domain_name, UNAVAIL))) + #ifdef USE_DOMAIN int pred1(void); CLANG_ENABLED_AVAILABILITY_DOMAIN(feature1); @@ -41,14 +51,67 @@ __attribute__((availability(domain:feature4, AVAIL))) int g4; __attribute__((availability(domain:feature4, UNAVAIL))) int g5; __attribute__((availability(domain:feature5, AVAIL))) void func21(void); __attribute__((availability(domain:feature5, UNAVAIL))) void func22(void); -__attribute__((availability(domain:deprecated_feature1, AVAIL))) void func23(void); -// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} -// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} -__attribute__((availability(domain:deprecated_feature1, UNAVAIL))) void func24(void); -// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} -// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} -__attribute__((availability(domain:deprecated_feature2, AVAIL))) void func25(void); -// expected-warning@-1 {{availability domain 'deprecated_feature2' is deprecated}} + +__attribute__((visibility("hidden"))) +__attribute__((availability(domain:deprecated_feature1, AVAIL))) +void deprecated_feature1_avail_func1(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:65}:"" + +__attribute__((availability(domain:deprecated_feature1, AVAIL), visibility("hidden"))) +void deprecated_feature1_avail_func2(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:16-[[@LINE-4]]:64}:"" + +__attribute__((visibility("hidden"), availability(domain:deprecated_feature1, AVAIL))) +void deprecated_feature1_avail_func3(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:36-[[@LINE-4]]:85}:"" + +FEATURE_AVAILABLE(deprecated_feature1) +void deprecated_feature1_avail_func4(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:39}:"" + +MY_FEATURE_AVAILABLE(deprecated_feature1) +void deprecated_feature1_avail_func4(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK-NOT: fix-it: + +__attribute__((visibility("hidden"))) +__attribute__((availability(domain:deprecated_feature1, UNAVAIL))) +void deprecated_feature1_unavail_func1(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:16-[[@LINE-4]]:65}:"unavailable" + +__attribute__((availability(domain:deprecated_feature1, UNAVAIL), visibility("hidden"))) +void deprecated_feature1_unavail_func2(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:16-[[@LINE-4]]:65}:"unavailable" + +__attribute__((visibility("hidden"), availability(domain:deprecated_feature1, UNAVAIL))) +void deprecated_feature1_unavail_func3(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:38-[[@LINE-4]]:87}:"unavailable" + +FEATURE_UNAVAILABLE(deprecated_feature1) +void deprecated_feature1_unavail_func4(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:41}:"__attribute__((unavailable))" + +__attribute__((availability(domain:deprecated_feature2, AVAIL))) +void deprecated_feature2_unavail_func1(void); +// expected-warning@-2 {{availability domain 'deprecated_feature2' is deprecated}} + #endif void test_unreachable_code(void) { diff --git a/clang/test/SemaObjC/feature-availability.m b/clang/test/SemaObjC/feature-availability.m index 05fa09c21b024..d90f718aca8bc 100644 --- a/clang/test/SemaObjC/feature-availability.m +++ b/clang/test/SemaObjC/feature-availability.m @@ -222,7 +222,7 @@ @interface DeprecatedC1 __attribute__((availability(domain:deprecated_feature1, UNAVAIL))) // expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} -// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} +// expected-warning@-2 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} @interface DeprecatedC2 @end From 4ba76621beaceb7e900346a3c7154ff4c964b403 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Wed, 1 Oct 2025 18:50:10 -0700 Subject: [PATCH 6/8] Emit fixits for macros that aren't complex --- clang/lib/Sema/SemaFeatureAvailability.cpp | 60 ++++++++++++++++++++-- clang/test/Sema/feature-availability.c | 53 +++++++++++++++++-- 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index 5a77cfebb47e4..37350cc25087a 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -244,19 +244,69 @@ void Sema::DiagnoseFeatureAvailabilityOfDecl(NamedDecl *D, diagnoseDeclFeatureAvailability(D, Locs.front(), Ctx, *this); } +static bool isSimpleFeatureAvailabiltyMacro(MacroInfo *Info) { + // Must match: + // __attribute__((availability(domain : id, id/numeric_constant))) + if (Info->getNumTokens() != 13) + return false; + + if (!Info->getReplacementToken(0).is(tok::kw___attribute) || + !Info->getReplacementToken(1).is(tok::l_paren) || + !Info->getReplacementToken(2).is(tok::l_paren)) + return false; + + if (const Token &Tk = Info->getReplacementToken(3); + !Tk.is(tok::identifier) || + Tk.getIdentifierInfo()->getName() != "availability") + return false; + + if (!Info->getReplacementToken(4).is(tok::l_paren)) + return false; + + if (const Token &Tk = Info->getReplacementToken(5); + !Tk.is(tok::identifier) || Tk.getIdentifierInfo()->getName() != "domain") + return false; + + if (!Info->getReplacementToken(6).is(tok::colon)) + return false; + + if (const Token &Tk = Info->getReplacementToken(7); !Tk.is(tok::identifier)) + return false; + + if (!Info->getReplacementToken(8).is(tok::comma)) + return false; + + if (const Token &Tk = Info->getReplacementToken(9); + !Tk.is(tok::identifier) && !Tk.is(tok::numeric_constant)) + return false; + + if (!Info->getReplacementToken(10).is(tok::r_paren) || + !Info->getReplacementToken(11).is(tok::r_paren) || + !Info->getReplacementToken(12).is(tok::r_paren)) + return false; + + return true; +} + void Sema::diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, SourceLocation AvailLoc, SourceLocation DomainLoc, bool IsUnavailable, const ParsedAttr *PA) { auto CreateFixIt = [&]() { - if (auto *MacroII = PA->getMacroIdentifier()) { - StringRef MacroName = MacroII->getName(); - if (MacroName != "FEATURE_AVAILABLE" && - MacroName != "FEATURE_UNAVAILABLE") + if (PA->getRange().getBegin().isMacroID()) { + auto *MacroII = PA->getMacroIdentifier(); + + // Macro identifier isn't always set. + if (!MacroII) + return FixItHint{}; + + MacroDefinition MD = PP.getMacroDefinition(MacroII); + MacroInfo *Info = MD.getMacroInfo(); + + if (!isSimpleFeatureAvailabiltyMacro(Info)) return FixItHint{}; - // FIXME: Check the file that defines the macros too. FileID FID = SourceMgr.getFileID(AvailLoc); const SrcMgr::ExpansionInfo *EI = &SourceMgr.getSLocEntry(FID).getExpansion(); diff --git a/clang/test/Sema/feature-availability.c b/clang/test/Sema/feature-availability.c index 07df1aeb70062..720b7d6128933 100644 --- a/clang/test/Sema/feature-availability.c +++ b/clang/test/Sema/feature-availability.c @@ -11,12 +11,28 @@ #define FEATURE_AVAILABLE(domain_name) \ __attribute__((availability(domain : domain_name, AVAIL))) -#define MY_FEATURE_AVAILABLE(domain_name) \ - __attribute__((availability(domain : domain_name, AVAIL))) - #define FEATURE_UNAVAILABLE(domain_name) \ __attribute__((availability(domain : domain_name, UNAVAIL))) +#define MY_FEATURE_AVAILABLE(domain_name) \ + /* abc */ __attribute__((availability(domain /*abc*/ : domain_name, 0))) // comment. + +#define VISIBILITY +#define MY_FEATURE_AVAILABLE2(domain_name) \ + VISIBILITY __attribute__((availability(domain : domain_name, 0))) + +#define MY_FEATURE_AVAILABLE3(domain_name) FEATURE_AVAILABLE(domain_name) + +#define AVAIL_ARGS domain: deprecated_feature1, 0 +#define MY_FEATURE_AVAILABLE4(domain_name) \ + __attribute__((availability(AVAIL_ARGS))) + +#define MY_FEATURE_AVAILABLE5(domain_name, function) \ + FEATURE_AVAILABLE(domain_name) \ + function + +#define DEPRECATED_FEATURE1_UNAVAILABLE __attribute__((availability(domain: deprecated_feature1, 1))) + #ifdef USE_DOMAIN int pred1(void); CLANG_ENABLED_AVAILABILITY_DOMAIN(feature1); @@ -78,11 +94,34 @@ void deprecated_feature1_avail_func4(void); // CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:39}:"" MY_FEATURE_AVAILABLE(deprecated_feature1) -void deprecated_feature1_avail_func4(void); +void deprecated_feature1_avail_func5(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:42}:"" + +MY_FEATURE_AVAILABLE2(deprecated_feature1) +void deprecated_feature1_avail_func6(void); // expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} // expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} // CHECK-NOT: fix-it: +MY_FEATURE_AVAILABLE3(deprecated_feature1) +void deprecated_feature1_avail_func7(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK-NOT: fix-it: + +MY_FEATURE_AVAILABLE4(deprecated_feature1) +void deprecated_feature1_avail_func8(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK-NOT: fix-it: + +MY_FEATURE_AVAILABLE5(deprecated_feature1, void deprecated_feature1_avail_func9(void);) +// expected-warning@-1 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK-NOT: fix-it: + __attribute__((visibility("hidden"))) __attribute__((availability(domain:deprecated_feature1, UNAVAIL))) void deprecated_feature1_unavail_func1(void); @@ -108,6 +147,12 @@ void deprecated_feature1_unavail_func4(void); // expected-warning@-3 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} // CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:41}:"__attribute__((unavailable))" +DEPRECATED_FEATURE1_UNAVAILABLE +void deprecated_feature1_unavail_func5(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{declaration is permanently unavailable because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:32}:"__attribute__((unavailable))" + __attribute__((availability(domain:deprecated_feature2, AVAIL))) void deprecated_feature2_unavail_func1(void); // expected-warning@-2 {{availability domain 'deprecated_feature2' is deprecated}} From c22a259eeeb253e2ed9edb1597dca7077f0bb127 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Mon, 6 Oct 2025 07:45:39 -0700 Subject: [PATCH 7/8] Cache the result --- clang/include/clang/Sema/Sema.h | 3 +++ clang/lib/Sema/SemaFeatureAvailability.cpp | 8 ++++++-- clang/test/Sema/feature-availability.c | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 663a03f49bbe8..348a1581caab4 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -142,6 +142,7 @@ class InitializedEntity; enum class LangAS : unsigned int; class LocalInstantiationScope; class LookupResult; +class MacroInfo; class MangleNumberingContext; typedef ArrayRef ModuleIdPath; class ModuleLoader; @@ -2676,6 +2677,8 @@ class Sema final : public SemaBase { bool IsUnavailable = false, const ParsedAttr *PA = nullptr); + llvm::SmallPtrSet SimpleFeatureAvailabiltyMacros; + /// Retrieve the current function, if any, that should be analyzed for /// potential availability violations. sema::FunctionScopeInfo *getCurFunctionAvailabilityContext(); diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index 37350cc25087a..b3c275f784a32 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -304,8 +304,12 @@ void Sema::diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, MacroDefinition MD = PP.getMacroDefinition(MacroII); MacroInfo *Info = MD.getMacroInfo(); - if (!isSimpleFeatureAvailabiltyMacro(Info)) - return FixItHint{}; + + if (!SimpleFeatureAvailabiltyMacros.count(Info)) { + if (!isSimpleFeatureAvailabiltyMacro(Info)) + return FixItHint{}; + SimpleFeatureAvailabiltyMacros.insert(Info); + } FileID FID = SourceMgr.getFileID(AvailLoc); const SrcMgr::ExpansionInfo *EI = diff --git a/clang/test/Sema/feature-availability.c b/clang/test/Sema/feature-availability.c index 720b7d6128933..f2b847550c30e 100644 --- a/clang/test/Sema/feature-availability.c +++ b/clang/test/Sema/feature-availability.c @@ -122,6 +122,12 @@ MY_FEATURE_AVAILABLE5(deprecated_feature1, void deprecated_feature1_avail_func9( // expected-warning@-2 {{attribute has no effect because 'deprecated_feature1' is always available}} // CHECK-NOT: fix-it: +FEATURE_AVAILABLE(deprecated_feature1) +void deprecated_feature1_avail_func10(void); +// expected-warning@-2 {{availability domain 'deprecated_feature1' is deprecated}} +// expected-warning@-3 {{attribute has no effect because 'deprecated_feature1' is always available}} +// CHECK: fix-it:"{{.*}}feature-availability.c":{[[@LINE-4]]:1-[[@LINE-4]]:39}:"" + __attribute__((visibility("hidden"))) __attribute__((availability(domain:deprecated_feature1, UNAVAIL))) void deprecated_feature1_unavail_func1(void); From e36e7de08d99a01163f40e97703ac2218d08acd8 Mon Sep 17 00:00:00 2001 From: Akira Hatanaka Date: Mon, 6 Oct 2025 10:50:51 -0700 Subject: [PATCH 8/8] Cache the result of analyzing complex macros too --- clang/include/clang/Sema/Sema.h | 3 ++- clang/lib/Sema/SemaFeatureAvailability.cpp | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 714def47d919a..2020f63987e06 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2677,7 +2677,8 @@ class Sema final : public SemaBase { bool IsUnavailable = false, const ParsedAttr *PA = nullptr); - llvm::SmallPtrSet SimpleFeatureAvailabiltyMacros; + /// A map that indicates whether a macro is simple. + llvm::SmallDenseMap SimpleFeatureAvailabiltyMacros; /// Retrieve the current function, if any, that should be analyzed for /// potential availability violations. diff --git a/clang/lib/Sema/SemaFeatureAvailability.cpp b/clang/lib/Sema/SemaFeatureAvailability.cpp index b3c275f784a32..82f2ef493fff4 100644 --- a/clang/lib/Sema/SemaFeatureAvailability.cpp +++ b/clang/lib/Sema/SemaFeatureAvailability.cpp @@ -304,13 +304,19 @@ void Sema::diagnoseDeprecatedAvailabilityDomain(StringRef DomainName, MacroDefinition MD = PP.getMacroDefinition(MacroII); MacroInfo *Info = MD.getMacroInfo(); - - if (!SimpleFeatureAvailabiltyMacros.count(Info)) { - if (!isSimpleFeatureAvailabiltyMacro(Info)) - return FixItHint{}; - SimpleFeatureAvailabiltyMacros.insert(Info); + bool IsSimple; + auto It = SimpleFeatureAvailabiltyMacros.find(Info); + + if (It == SimpleFeatureAvailabiltyMacros.end()) { + IsSimple = isSimpleFeatureAvailabiltyMacro(Info); + SimpleFeatureAvailabiltyMacros[Info] = IsSimple; + } else { + IsSimple = It->second; } + if (!IsSimple) + return FixItHint{}; + FileID FID = SourceMgr.getFileID(AvailLoc); const SrcMgr::ExpansionInfo *EI = &SourceMgr.getSLocEntry(FID).getExpansion();