Skip to content

Commit e199eb8

Browse files
committed
Sema: Ban unavailable deinits.
Destructors are always called if declared, so allowing deinit to be declared as unavailable (or potentially unavailable) creates a type checking loophole that allows unavailable code to execute at runtime: ``` class C { @available(*, unavailable) deinit { print("Oops") } } _ = C() // prints "Oops" ``` Resolves rdar://106409012 and #63854.
1 parent 6ef77d3 commit e199eb8

File tree

4 files changed

+119
-25
lines changed

4 files changed

+119
-25
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6122,6 +6122,13 @@ WARNING(availability_query_useless_enclosing_scope, none,
61226122
NOTE(availability_query_useless_enclosing_scope_here, none,
61236123
"enclosing scope here", ())
61246124

6125+
ERROR(availability_deinit_no_potential, none,
6126+
"deinitializer cannot be marked potentially unavailable with "
6127+
"'@available'", ())
6128+
6129+
ERROR(availability_deinit_no_unavailable, none,
6130+
"deinitializer cannot be marked unavailable with '@available'", ())
6131+
61256132
ERROR(availability_global_script_no_potential,
61266133
none, "global variable cannot be marked potentially "
61276134
"unavailable with '@available' in script mode", ())

lib/Sema/TypeCheckAttr.cpp

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,33 +1858,14 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
18581858
if (Ctx.LangOpts.DisableAvailabilityChecking)
18591859
return;
18601860

1861+
// FIXME: This seems like it could be diagnosed during parsing instead.
18611862
while (attr->IsSPI) {
18621863
if (attr->hasPlatform() && attr->Introduced.has_value())
18631864
break;
18641865
diagnoseAndRemoveAttr(attr, diag::spi_available_malformed);
18651866
break;
18661867
}
18671868

1868-
if (auto *PD = dyn_cast<ProtocolDecl>(D->getDeclContext())) {
1869-
if (auto *VD = dyn_cast<ValueDecl>(D)) {
1870-
if (VD->isProtocolRequirement()) {
1871-
if (attr->isActivePlatform(Ctx) ||
1872-
attr->isLanguageVersionSpecific() ||
1873-
attr->isPackageDescriptionVersionSpecific()) {
1874-
auto versionAvailability = attr->getVersionAvailability(Ctx);
1875-
if (attr->isUnconditionallyUnavailable() ||
1876-
versionAvailability == AvailableVersionComparison::Obsoleted ||
1877-
versionAvailability == AvailableVersionComparison::Unavailable) {
1878-
if (!PD->isObjC()) {
1879-
diagnoseAndRemoveAttr(attr, diag::unavailable_method_non_objc_protocol);
1880-
return;
1881-
}
1882-
}
1883-
}
1884-
}
1885-
}
1886-
}
1887-
18881869
if (attr->isNoAsync()) {
18891870
const DeclContext * dctx = dyn_cast<DeclContext>(D);
18901871
bool isAsyncDeclContext = dctx && dctx->isAsyncContext();
@@ -1911,19 +1892,45 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
19111892
D->getASTContext().Diags.diagnose(
19121893
D->getLoc(), diag::invalid_decl_attribute, attr);
19131894
}
1914-
19151895
}
19161896

19171897
// Skip the remaining diagnostics in swiftinterfaces.
19181898
auto *SF = D->getDeclContext()->getParentSourceFile();
19191899
if (SF && SF->Kind == SourceFileKind::Interface)
19201900
return;
19211901

1922-
if (!attr->hasPlatform() || !attr->isActivePlatform(Ctx) ||
1923-
!attr->Introduced.has_value()) {
1902+
// The remaining diagnostics are only for attributes that are active for the
1903+
// current target triple.
1904+
if (!attr->isActivePlatform(Ctx) && !attr->isLanguageVersionSpecific() &&
1905+
!attr->isPackageDescriptionVersionSpecific())
19241906
return;
1907+
1908+
SourceLoc attrLoc = attr->getLocation();
1909+
auto versionAvailability = attr->getVersionAvailability(Ctx);
1910+
if (versionAvailability == AvailableVersionComparison::Obsoleted ||
1911+
versionAvailability == AvailableVersionComparison::Unavailable) {
1912+
if (auto cannotBeUnavailable =
1913+
TypeChecker::diagnosticIfDeclCannotBeUnavailable(D)) {
1914+
diagnose(attrLoc, cannotBeUnavailable.value());
1915+
return;
1916+
}
1917+
1918+
if (auto *PD = dyn_cast<ProtocolDecl>(D->getDeclContext())) {
1919+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
1920+
if (VD->isProtocolRequirement() && !PD->isObjC()) {
1921+
diagnoseAndRemoveAttr(attr,
1922+
diag::unavailable_method_non_objc_protocol);
1923+
return;
1924+
}
1925+
}
1926+
}
19251927
}
19261928

1929+
// The remaining diagnostics are only for attributes with introduced versions
1930+
// for specific platforms.
1931+
if (!attr->hasPlatform() || !attr->Introduced.has_value())
1932+
return;
1933+
19271934
// Make sure there isn't a more specific attribute we should be using instead.
19281935
// findMostSpecificActivePlatform() is O(N), so only do this if we're checking
19291936
// an iOS attribute while building for macCatalyst.
@@ -1934,8 +1941,6 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
19341941
}
19351942
}
19361943

1937-
SourceLoc attrLoc = attr->getLocation();
1938-
19391944
// Find the innermost enclosing declaration with an availability
19401945
// range annotation and ensure that this attribute's available version range
19411946
// is fully contained within that declaration's range. If there is no such
@@ -4459,6 +4464,10 @@ Optional<Diag<>>
44594464
TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(const Decl *D) {
44604465
auto *DC = D->getDeclContext();
44614466

4467+
// A destructor is always called if declared.
4468+
if (auto *DD = dyn_cast<DestructorDecl>(D))
4469+
return diag::availability_deinit_no_potential;
4470+
44624471
if (auto *VD = dyn_cast<VarDecl>(D)) {
44634472
if (!VD->hasStorageOrWrapsStorage())
44644473
return None;
@@ -4493,6 +4502,27 @@ TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(const Decl *D) {
44934502
return None;
44944503
}
44954504

4505+
Optional<Diag<>>
4506+
TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
4507+
auto parentIsUnavailable = [](const Decl *D) -> bool {
4508+
if (auto *parent =
4509+
AvailabilityInference::parentDeclForInferredAvailability(D)) {
4510+
return parent->getSemanticUnavailableAttr() != None;
4511+
}
4512+
return false;
4513+
};
4514+
4515+
// A destructor is always called if declared.
4516+
if (auto *DD = dyn_cast<DestructorDecl>(D)) {
4517+
if (parentIsUnavailable(D))
4518+
return None;
4519+
4520+
return diag::availability_deinit_no_unavailable;
4521+
}
4522+
4523+
return None;
4524+
}
4525+
44964526
static bool shouldBlockImplicitDynamic(Decl *D) {
44974527
if (D->getAttrs().hasAttribute<NonObjCAttr>() ||
44984528
D->getAttrs().hasAttribute<SILGenNameAttr>() ||

lib/Sema/TypeChecker.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,11 @@ TypeRefinementContext *getOrBuildTypeRefinementContext(SourceFile *SF);
10601060
Optional<Diag<>>
10611061
diagnosticIfDeclCannotBePotentiallyUnavailable(const Decl *D);
10621062

1063+
/// Returns a diagnostic indicating why the declaration cannot be annotated
1064+
/// with an @available() attribute indicating it is unavailable or None if this
1065+
/// is allowed.
1066+
Optional<Diag<>> diagnosticIfDeclCannotBeUnavailable(const Decl *D);
1067+
10631068
/// Same as \c checkDeclarationAvailability but doesn't give a reason for
10641069
/// unavailability.
10651070
bool isDeclarationUnavailable(

test/Sema/availability_deinit.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.50
2+
3+
// REQUIRES: OS=macosx
4+
5+
@available(*, unavailable)
6+
class Unavailable {
7+
deinit {}
8+
}
9+
10+
@available(*, unavailable)
11+
class UnavailableWithUnavailableDeinit {
12+
@available(*, unavailable)
13+
deinit {}
14+
}
15+
16+
@available(*, unavailable)
17+
enum UnavailableEnum {
18+
class NestedWithUnavailableDeinit {
19+
@available(*, unavailable)
20+
deinit {}
21+
}
22+
}
23+
24+
class DeinitUnavailable {
25+
@available(*, unavailable) // expected-error {{deinitializer cannot be marked unavailable with '@available'}}
26+
deinit {}
27+
}
28+
29+
class DeinitUnavailableMacOS {
30+
@available(macOS, unavailable) // expected-error {{deinitializer cannot be marked unavailable with '@available'}}
31+
deinit {}
32+
}
33+
34+
class AvailableAtDeploymentTargetDeinit {
35+
@available(macOS 10.50, *)
36+
deinit {}
37+
}
38+
39+
class PotentiallyUnavailableDeinit {
40+
@available(macOS 10.51, *) // expected-error {{deinitializer cannot be marked potentially unavailable with '@available'}}
41+
deinit {}
42+
}
43+
44+
@available(macOS 10.51, *)
45+
func funcAvailable10_51() {}
46+
47+
class AlwaysAvailable { // expected-note {{add @available attribute to enclosing class}}
48+
deinit {
49+
funcAvailable10_51() // expected-error {{'funcAvailable10_51()' is only available in macOS 10.51 or newer}}
50+
// expected-note@-1 {{add 'if #available' version check}}
51+
}
52+
}

0 commit comments

Comments
 (0)