Skip to content

Commit 1d3d8a9

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 c7026f6 commit 1d3d8a9

File tree

5 files changed

+84
-57
lines changed

5 files changed

+84
-57
lines changed

include/swift/AST/AvailabilityConstraint.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,11 @@ class AvailabilityConstraint {
133133
/// Returns the required range for `IntroducedInNewerVersion` requirements, or
134134
/// `std::nullopt` otherwise.
135135
std::optional<AvailabilityRange>
136-
getRequiredNewerAvailabilityRange(ASTContext &ctx) const;
136+
getRequiredNewerAvailabilityRange(const ASTContext &ctx) const;
137137

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

143143
/// Represents a set of availability constraints that restrict use of a
@@ -149,9 +149,14 @@ class DeclAvailabilityConstraints {
149149
public:
150150
DeclAvailabilityConstraints() {}
151151

152-
void addConstraint(const AvailabilityConstraint &constraint) {
153-
constraints.emplace_back(constraint);
154-
}
152+
/// Inserts a constraint into the collection. If the constraint has the same
153+
/// kind and domain as an existing constraint, then the weaker constraint is
154+
/// discarded.
155+
void addConstraint(const AvailabilityConstraint &constraint,
156+
const ASTContext &ctx);
157+
158+
/// Returns the strongest availability constraint or `std::nullopt` if empty.
159+
std::optional<AvailabilityConstraint> getPrimaryConstraint() const;
155160

156161
using const_iterator = Storage::const_iterator;
157162
const_iterator begin() const { return constraints.begin(); }

lib/AST/Availability.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -575,11 +575,10 @@ bool Decl::isUnavailableInCurrentSwiftVersion() const {
575575

576576
std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
577577
auto context = AvailabilityContext::forDeploymentTarget(getASTContext());
578-
auto constraints = swift::getAvailabilityConstraintsForDecl(this, context);
579-
580-
for (auto const &constraint : constraints) {
581-
if (constraint.isUnavailable())
582-
return constraint.getAttr();
578+
if (auto constraint = getAvailabilityConstraintsForDecl(this, context)
579+
.getPrimaryConstraint()) {
580+
if (constraint->isUnavailable())
581+
return constraint->getAttr();
583582
}
584583

585584
return std::nullopt;

lib/AST/AvailabilityConstraint.cpp

Lines changed: 58 additions & 3 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,59 @@ bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const {
4445
/*forRuntimeQuery=*/true);
4546
}
4647

48+
void DeclAvailabilityConstraints::addConstraint(
49+
const AvailabilityConstraint &constraint, const ASTContext &ctx) {
50+
51+
auto existing = llvm::find_if(
52+
constraints, [&constraint](AvailabilityConstraint &existing) {
53+
return constraint.getKind() == existing.getKind() &&
54+
constraint.getDomain() == existing.getDomain();
55+
});
56+
57+
if (existing == constraints.end()) {
58+
constraints.emplace_back(constraint);
59+
return;
60+
}
61+
62+
// Since the constraints have matching kinds and domains, choose the stronger
63+
// constraint to keep.
64+
switch (constraint.getKind()) {
65+
case AvailabilityConstraint::Kind::Unavailable:
66+
// All unavailable constraints are considered equal, just keep the first
67+
// we encounter.
68+
break;
69+
case AvailabilityConstraint::Kind::PotentiallyAvailable:
70+
// Replace the existing constraint if the required version is higher.
71+
auto existingRange = existing->getRequiredNewerAvailabilityRange(ctx);
72+
auto newRange = constraint.getRequiredNewerAvailabilityRange(ctx);
73+
if (existingRange && newRange && newRange->isContainedIn(*existingRange))
74+
*existing = constraint;
75+
break;
76+
}
77+
}
78+
79+
std::optional<AvailabilityConstraint>
80+
DeclAvailabilityConstraints::getPrimaryConstraint() const {
81+
std::optional<AvailabilityConstraint> result;
82+
83+
auto isStrongerConstraint = [](const AvailabilityConstraint &lhs,
84+
const AvailabilityConstraint &rhs) {
85+
// Pick the strongest constraint. Constraint kinds are defined in descending
86+
// order of strength.
87+
if (lhs.getKind() != rhs.getKind())
88+
return lhs.getKind() < rhs.getKind();
89+
90+
return false;
91+
};
92+
93+
for (auto const &constraint : constraints) {
94+
if (!result || isStrongerConstraint(constraint, *result))
95+
result.emplace(constraint);
96+
}
97+
98+
return result;
99+
}
100+
47101
static bool
48102
isInsideCompatibleUnavailableDeclaration(const Decl *decl,
49103
const SemanticAvailableAttr &attr,
@@ -132,6 +186,7 @@ static void
132186
getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints,
133187
const Decl *decl,
134188
const AvailabilityContext &context) {
189+
auto &ctx = decl->getASTContext();
135190
auto activePlatformDomain = activePlatformDomainForDecl(decl);
136191

137192
for (auto attr :
@@ -143,7 +198,7 @@ getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints,
143198

144199
if (auto constraint =
145200
swift::getAvailabilityConstraintForAttr(decl, attr, context))
146-
constraints.addConstraint(*constraint);
201+
constraints.addConstraint(*constraint, ctx);
147202
}
148203
}
149204

lib/Sema/TypeCheckAvailability.cpp

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

31303095
std::optional<AvailabilityConstraint>

test/decl/ext/objc_implementation_direct_to_storage.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@
44

55
import objc_implementation_internal
66

7+
// FIXME: [availability] An implementation that is less available than the interface it implements should be diagnosed
78
@available(*, unavailable)
89
@objc @implementation extension ObjCPropertyTest {
9-
// FIXME: Shouldn't this be on the `@available` above?
10-
// expected-note@+1 {{'prop1' has been explicitly marked unavailable here}}
1110
let prop1: Int32
1211

13-
// expected-note@+1 2 {{'prop2' has been explicitly marked unavailable here}}
1412
var prop2: Int32 {
1513
didSet {
16-
_ = prop2 // expected-error {{'prop2' is unavailable}}
14+
_ = prop2
1715
}
1816
}
1917

2018
override init() {
21-
self.prop1 = 1 // expected-error {{'prop1' is unavailable}}
22-
self.prop2 = 2 // expected-error {{'prop2' is unavailable}}
19+
self.prop1 = 1
20+
self.prop2 = 2
2321
super.init()
2422
}
2523

@@ -28,3 +26,8 @@ import objc_implementation_internal
2826
_ = self.prop2
2927
}
3028
}
29+
30+
func takesObjCPropertyTest(_ o: ObjCPropertyTest) {
31+
_ = o.prop1
32+
_ = o.prop2
33+
}

0 commit comments

Comments
 (0)