Skip to content

Commit 91b3b16

Browse files
committed
AST: Use consolidated availability constraint query for diagnostics.
Switch to calling `swift::getAvailabilityConstraintsForDecl()` to get the unsatisfied availability constraints that should be diagnosed. This was intended to be NFC, but it turns out it fixed a bug in the recently introduced objc_implementation_direct_to_storage.swift test. In the test, the stored properties are as unavailable as the context that is accessing them so the accesses should not be diagnosed. However, this test demonstrates a bigger issue with `@objc @implementation`, which is that it allows the implementations of Obj-C interfaces to be less available than the interface, which effectively provides an availability checking loophole that can be used to invoke unavailable code.
1 parent da02cd0 commit 91b3b16

File tree

7 files changed

+139
-128
lines changed

7 files changed

+139
-128
lines changed

include/swift/AST/AvailabilityConstraint.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class Decl;
3333
/// certain context.
3434
class AvailabilityConstraint {
3535
public:
36+
/// The reason that the availability constraint is unsatisfied.
37+
///
38+
/// NOTE: The order of this enum matters. Reasons are defined in descending
39+
/// priority order.
3640
enum class Reason {
3741
/// The declaration is referenced in a context in which it is generally
3842
/// unavailable. For example, a reference to a declaration that is
@@ -133,38 +137,34 @@ class AvailabilityConstraint {
133137
/// Returns the required range for `IntroducedInNewerVersion` requirements, or
134138
/// `std::nullopt` otherwise.
135139
std::optional<AvailabilityRange>
136-
getRequiredNewerAvailabilityRange(ASTContext &ctx) const;
140+
getRequiredNewerAvailabilityRange(const ASTContext &ctx) const;
137141

138142
/// Some availability constraints are active for type-checking but cannot
139143
/// be translated directly into an `if #available(...)` runtime query.
140-
bool isActiveForRuntimeQueries(ASTContext &ctx) const;
144+
bool isActiveForRuntimeQueries(const ASTContext &ctx) const;
141145
};
142146

143147
/// Represents a set of availability constraints that restrict use of a
144-
/// declaration in a particular context.
148+
/// declaration in a particular context. There can only be one active constraint
149+
/// for a given `AvailabilityDomain`, but there may be multiple active
150+
/// constraints from separate domains.
145151
class DeclAvailabilityConstraints {
146152
using Storage = llvm::SmallVector<AvailabilityConstraint, 4>;
147153
Storage constraints;
148154

149155
public:
150156
DeclAvailabilityConstraints() {}
157+
DeclAvailabilityConstraints(const Storage &&constraints)
158+
: constraints(constraints) {}
151159

152-
void addConstraint(const AvailabilityConstraint &constraint) {
153-
constraints.emplace_back(constraint);
154-
}
160+
/// Returns the strongest availability constraint or `std::nullopt` if empty.
161+
std::optional<AvailabilityConstraint> getPrimaryConstraint() const;
155162

156163
using const_iterator = Storage::const_iterator;
157164
const_iterator begin() const { return constraints.begin(); }
158165
const_iterator end() const { return constraints.end(); }
159166
};
160167

161-
/// Returns the `AvailabilityConstraint` that describes how \p attr restricts
162-
/// use of \p decl in \p context or `std::nullopt` if there is no restriction.
163-
std::optional<AvailabilityConstraint>
164-
getAvailabilityConstraintForAttr(const Decl *decl,
165-
const SemanticAvailableAttr &attr,
166-
const AvailabilityContext &context);
167-
168168
/// Returns the set of availability constraints that restrict use of \p decl
169169
/// when it is referenced from the given context. In other words, it is the
170170
/// collection of of `@available` attributes with unsatisfied conditions.

lib/AST/Availability.cpp

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -573,49 +573,13 @@ bool Decl::isUnavailableInCurrentSwiftVersion() const {
573573
return false;
574574
}
575575

576-
std::optional<SemanticAvailableAttr> getDeclUnavailableAttr(const Decl *D) {
577-
auto &ctx = D->getASTContext();
578-
std::optional<SemanticAvailableAttr> result;
579-
auto bestActive = D->getActiveAvailableAttrForCurrentPlatform();
580-
581-
for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
582-
// If this is a platform-specific attribute and it isn't the most
583-
// specific attribute for the current platform, we're done.
584-
if (attr.isPlatformSpecific() && (!bestActive || attr != bestActive))
585-
continue;
586-
587-
// Unconditional unavailable.
588-
if (attr.isUnconditionallyUnavailable())
589-
return attr;
590-
591-
switch (attr.getVersionAvailability(ctx)) {
592-
case AvailableVersionComparison::Available:
593-
case AvailableVersionComparison::PotentiallyUnavailable:
594-
break;
595-
596-
case AvailableVersionComparison::Obsoleted:
597-
case AvailableVersionComparison::Unavailable:
598-
result.emplace(attr);
599-
break;
600-
}
601-
}
602-
return result;
603-
}
604-
605576
std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
606-
if (auto attr = getDeclUnavailableAttr(this))
607-
return attr;
608-
609-
// If D is an extension member, check if the extension is unavailable.
610-
//
611-
// Skip decls imported from Clang, they could be associated to the wrong
612-
// extension and inherit undesired unavailability. The ClangImporter
613-
// associates Objective-C protocol members to the first category where the
614-
// protocol is directly or indirectly adopted, no matter its availability
615-
// and the availability of other categories. rdar://problem/53956555
616-
if (!getClangNode())
617-
if (auto ext = dyn_cast<ExtensionDecl>(getDeclContext()))
618-
return ext->getUnavailableAttr();
577+
auto context = AvailabilityContext::forDeploymentTarget(getASTContext());
578+
if (auto constraint = getAvailabilityConstraintsForDecl(this, context)
579+
.getPrimaryConstraint()) {
580+
if (constraint->isUnavailable())
581+
return constraint->getAttr();
582+
}
619583

620584
return std::nullopt;
621585
}

lib/AST/AvailabilityConstraint.cpp

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ PlatformKind AvailabilityConstraint::getPlatform() const {
2424

2525
std::optional<AvailabilityRange>
2626
AvailabilityConstraint::getRequiredNewerAvailabilityRange(
27-
ASTContext &ctx) const {
27+
const ASTContext &ctx) const {
2828
switch (getReason()) {
2929
case Reason::UnconditionallyUnavailable:
3030
case Reason::Obsoleted:
@@ -35,7 +35,8 @@ AvailabilityConstraint::getRequiredNewerAvailabilityRange(
3535
}
3636
}
3737

38-
bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const {
38+
bool AvailabilityConstraint::isActiveForRuntimeQueries(
39+
const ASTContext &ctx) const {
3940
if (getAttr().getPlatform() == PlatformKind::none)
4041
return true;
4142

@@ -44,6 +45,74 @@ bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const {
4445
/*forRuntimeQuery=*/true);
4546
}
4647

48+
static bool constraintIsStronger(const AvailabilityConstraint &lhs,
49+
const AvailabilityConstraint &rhs) {
50+
DEBUG_ASSERT(lhs.getDomain() == rhs.getDomain());
51+
52+
// If the constraints have matching domains but different reasons, the
53+
// constraint with the lowest reason is "strongest".
54+
if (lhs.getReason() != rhs.getReason())
55+
return lhs.getReason() < rhs.getReason();
56+
57+
switch (lhs.getReason()) {
58+
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
59+
// Just keep the first.
60+
return false;
61+
62+
case AvailabilityConstraint::Reason::Obsoleted:
63+
// Pick the earliest obsoleted version.
64+
return *lhs.getAttr().getObsoleted() < *rhs.getAttr().getObsoleted();
65+
66+
case AvailabilityConstraint::Reason::IntroducedInLaterVersion:
67+
case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion:
68+
// Pick the latest introduced version.
69+
return *lhs.getAttr().getIntroduced() > *rhs.getAttr().getIntroduced();
70+
}
71+
}
72+
73+
void addConstraint(llvm::SmallVector<AvailabilityConstraint, 4> &constraints,
74+
const AvailabilityConstraint &constraint,
75+
const ASTContext &ctx) {
76+
77+
auto iter = llvm::find_if(
78+
constraints, [&constraint](AvailabilityConstraint &existing) {
79+
return constraint.getDomain() == existing.getDomain();
80+
});
81+
82+
// There's no existing constraint for the same domain so just add it.
83+
if (iter == constraints.end()) {
84+
constraints.emplace_back(constraint);
85+
return;
86+
}
87+
88+
if (constraintIsStronger(constraint, *iter)) {
89+
constraints.erase(iter);
90+
constraints.emplace_back(constraint);
91+
}
92+
}
93+
94+
std::optional<AvailabilityConstraint>
95+
DeclAvailabilityConstraints::getPrimaryConstraint() const {
96+
std::optional<AvailabilityConstraint> result;
97+
98+
auto isStrongerConstraint = [](const AvailabilityConstraint &lhs,
99+
const AvailabilityConstraint &rhs) {
100+
// Pick the strongest constraint. Constraint kinds are defined in descending
101+
// order of strength.
102+
if (lhs.getKind() != rhs.getKind())
103+
return lhs.getKind() < rhs.getKind();
104+
105+
return false;
106+
};
107+
108+
for (auto const &constraint : constraints) {
109+
if (!result || isStrongerConstraint(constraint, *result))
110+
result.emplace(constraint);
111+
}
112+
113+
return result;
114+
}
115+
47116
static bool
48117
isInsideCompatibleUnavailableDeclaration(const Decl *decl,
49118
const SemanticAvailableAttr &attr,
@@ -65,13 +134,12 @@ isInsideCompatibleUnavailableDeclaration(const Decl *decl,
65134
return context.containsUnavailableDomain(domain);
66135
}
67136

68-
std::optional<AvailabilityConstraint>
69-
swift::getAvailabilityConstraintForAttr(const Decl *decl,
70-
const SemanticAvailableAttr &attr,
71-
const AvailabilityContext &context) {
72-
if (isInsideCompatibleUnavailableDeclaration(decl, attr, context))
73-
return std::nullopt;
74-
137+
/// Returns the `AvailabilityConstraint` that describes how \p attr restricts
138+
/// use of \p decl in \p context or `std::nullopt` if there is no restriction.
139+
static std::optional<AvailabilityConstraint>
140+
getAvailabilityConstraintForAttr(const Decl *decl,
141+
const SemanticAvailableAttr &attr,
142+
const AvailabilityContext &context) {
75143
if (attr.isUnconditionallyUnavailable())
76144
return AvailabilityConstraint::unconditionallyUnavailable(attr);
77145

@@ -128,10 +196,10 @@ activePlatformDomainForDecl(const Decl *decl) {
128196
return activeDomain;
129197
}
130198

131-
static void
132-
getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints,
133-
const Decl *decl,
134-
const AvailabilityContext &context) {
199+
static void getAvailabilityConstraintsForDecl(
200+
llvm::SmallVector<AvailabilityConstraint, 4> &constraints, const Decl *decl,
201+
const AvailabilityContext &context) {
202+
auto &ctx = decl->getASTContext();
135203
auto activePlatformDomain = activePlatformDomainForDecl(decl);
136204

137205
for (auto attr :
@@ -141,20 +209,27 @@ getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints,
141209
!activePlatformDomain->contains(domain))
142210
continue;
143211

144-
if (auto constraint =
145-
swift::getAvailabilityConstraintForAttr(decl, attr, context))
146-
constraints.addConstraint(*constraint);
212+
if (auto constraint = getAvailabilityConstraintForAttr(decl, attr, context))
213+
addConstraint(constraints, *constraint, ctx);
147214
}
215+
216+
// After resolving constraints, remove any constraints that indicate the
217+
// declaration is unconditionally unavailable in a domain for which
218+
// the context is already unavailable.
219+
llvm::erase_if(constraints, [&](const AvailabilityConstraint &constraint) {
220+
return isInsideCompatibleUnavailableDeclaration(decl, constraint.getAttr(),
221+
context);
222+
});
148223
}
149224

150225
DeclAvailabilityConstraints
151226
swift::getAvailabilityConstraintsForDecl(const Decl *decl,
152227
const AvailabilityContext &context) {
153-
DeclAvailabilityConstraints constraints;
228+
llvm::SmallVector<AvailabilityConstraint, 4> constraints;
154229

155230
// Generic parameters are always available.
156231
if (isa<GenericTypeParamDecl>(decl))
157-
return constraints;
232+
return DeclAvailabilityConstraints();
158233

159234
decl = abstractSyntaxDeclForAvailableAttribute(decl);
160235

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3088,44 +3088,9 @@ bool diagnoseExplicitUnavailability(SourceLoc loc,
30883088
std::optional<AvailabilityConstraint>
30893089
swift::getUnsatisfiedAvailabilityConstraint(
30903090
const Decl *decl, AvailabilityContext availabilityContext) {
3091-
auto &ctx = decl->getASTContext();
3092-
3093-
// Generic parameters are always available.
3094-
if (isa<GenericTypeParamDecl>(decl))
3095-
return std::nullopt;
3096-
3097-
if (auto attr = decl->getUnavailableAttr()) {
3098-
if (isInsideCompatibleUnavailableDeclaration(decl, availabilityContext,
3099-
*attr))
3100-
return std::nullopt;
3101-
3102-
switch (attr->getVersionAvailability(ctx)) {
3103-
case AvailableVersionComparison::Available:
3104-
case AvailableVersionComparison::PotentiallyUnavailable:
3105-
llvm_unreachable("Decl should be unavailable");
3106-
3107-
case AvailableVersionComparison::Unavailable:
3108-
if ((attr->isSwiftLanguageModeSpecific() ||
3109-
attr->isPackageDescriptionVersionSpecific()) &&
3110-
attr->getIntroduced().has_value())
3111-
return AvailabilityConstraint::introducedInLaterVersion(*attr);
3112-
3113-
return AvailabilityConstraint::unconditionallyUnavailable(*attr);
3114-
3115-
case AvailableVersionComparison::Obsoleted:
3116-
return AvailabilityConstraint::obsoleted(*attr);
3117-
}
3118-
}
3119-
3120-
// Check whether the declaration is available in a newer platform version.
3121-
if (auto rangeAttr = decl->getAvailableAttrForPlatformIntroduction()) {
3122-
auto range = rangeAttr->getIntroducedRange(ctx);
3123-
if (!availabilityContext.getPlatformRange().isContainedIn(range))
3124-
return AvailabilityConstraint::introducedInLaterDynamicVersion(
3125-
*rangeAttr);
3126-
}
3127-
3128-
return std::nullopt;
3091+
auto constraints =
3092+
swift::getAvailabilityConstraintsForDecl(decl, availabilityContext);
3093+
return constraints.getPrimaryConstraint();
31293094
}
31303095

31313096
std::optional<AvailabilityConstraint>

test/Sema/availability_scopes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ extension SomeEnum {
358358
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeEnum
359359
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeEnum
360360
// CHECK-NEXT: {{^}} (decl_implicit version=50 unavailable=macOS decl=availableMacOS_52
361-
// CHECK-NEXT: {{^}} (decl version=52 unavailable=macOS decl=availableMacOS_52
361+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=availableMacOS_52
362362
// CHECK-NEXT: {{^}} (decl version=50 unavailable=* decl=neverAvailable()
363363

364364
@available(macOS, unavailable)

test/attr/attr_availability_transitive_osx.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,9 @@ func available_func_call_extension_methods(_ e: ExtendMe) { // expected-note {{a
533533
// expected-note@-1 {{add 'if #available' version check}}
534534
}
535535

536+
@available(OSX, obsoleted: 10.9)
537+
struct OSXObsoleted {} // expected-note 2 {{'OSXObsoleted' was obsoleted in macOS 10.9}}
538+
536539
@available(OSX, unavailable)
537540
@available(OSX, introduced: 99)
538541
struct OSXUnavailableAndIntroducedInFuture {}
@@ -547,32 +550,33 @@ struct OSXIntroducedInFutureAndUnavailable {}
547550
@available(OSX, unavailable)
548551
func osx_unavailable_func(
549552
_ s1: OSXFutureAvailable,
550-
_ s2: OSXUnavailableAndIntroducedInFuture,
551-
_ s3: OSXUnavailableAndIntroducedInFutureSameAttribute,
552-
_ s4: OSXIntroducedInFutureAndUnavailable,
553+
_ s2: OSXObsoleted,
554+
_ s3: OSXUnavailableAndIntroducedInFuture,
555+
_ s4: OSXUnavailableAndIntroducedInFutureSameAttribute,
556+
_ s5: OSXIntroducedInFutureAndUnavailable,
553557
) -> (
554558
OSXFutureAvailable,
559+
OSXObsoleted,
555560
OSXUnavailableAndIntroducedInFuture,
556561
OSXUnavailableAndIntroducedInFutureSameAttribute,
557562
OSXIntroducedInFutureAndUnavailable
558563
) {
564+
// FIXME: [availability] Stop diagnosing potential unavailability or obsoletion in an unavailable context.
559565
_ = OSXFutureAvailable() // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}}
560566
// expected-note@-1 {{add 'if #available' version check}}
561-
// FIXME: [availability] The following diagnostic is incorrect
562-
_ = OSXUnavailableAndIntroducedInFuture() // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}}
563-
// expected-note@-1 {{add 'if #available' version check}}
567+
_ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}}
568+
_ = OSXUnavailableAndIntroducedInFuture()
564569
_ = OSXUnavailableAndIntroducedInFutureSameAttribute()
565570
_ = OSXIntroducedInFutureAndUnavailable()
566571

567572
func takesType<T>(_ t: T.Type) {}
568573
takesType(OSXFutureAvailable.self) // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}}
569574
// expected-note@-1 {{add 'if #available' version check}}
570-
// FIXME: [availability] The following diagnostic is incorrect
571-
takesType(OSXUnavailableAndIntroducedInFuture.self) // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}}
572-
// expected-note@-1 {{add 'if #available' version check}}
575+
takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}}
576+
takesType(OSXUnavailableAndIntroducedInFuture.self)
573577
takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self)
574578
takesType(OSXIntroducedInFutureAndUnavailable.self)
575579

576-
return (s1, s2, s3, s4)
580+
return (s1, s2, s3, s4, s5)
577581
}
578582

0 commit comments

Comments
 (0)