Skip to content

Commit 9812c90

Browse files
committed
Sema: Ban unavailable stored properties.
Marking a stored property as unavailable with `@available` should be banned, just like it already is banned for potential unavailability. Unavailable code is not supposed be reachable at runtime, but unavailable properties with storage create a back door into executing arbitrary unavailable code at runtime: ``` @available(*, unavailable) struct Unavailable { init() { print("Unavailable.init()") } } struct S { @available(*, unavailable) var x = Unavailable() } _ = S() // prints "Unavailable.init()" ``` Resolves rdar://107449845
1 parent 7dc313f commit 9812c90

File tree

6 files changed

+160
-5
lines changed

6 files changed

+160
-5
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
44

55
## Swift 5.9
66

7+
* Marking stored properties as unavailable with `@available` has been banned,
8+
closing an unintentional soundness hole that had allowed arbitrary
9+
unavailable code to run and unavailable type metadata to be used at runtime:
10+
11+
```swift
12+
@available(*, unavailable)
13+
struct Unavailable {
14+
init() {
15+
print("Unavailable.init()")
16+
}
17+
}
18+
19+
struct S {
20+
@available(*, unavailable)
21+
var x = Unavailable()
22+
}
23+
24+
_ = S() // prints "Unavailable.init()"
25+
```
26+
27+
Marking `deinit` as unavailable has also been banned for similar reasons.
28+
729
* [SE-0366][]:
830

931
The lifetime of a local variable value can be explicitly ended using the

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6133,10 +6133,18 @@ ERROR(availability_global_script_no_potential,
61336133
none, "global variable cannot be marked potentially "
61346134
"unavailable with '@available' in script mode", ())
61356135

6136+
ERROR(availability_global_script_no_unavailable,
6137+
none, "global variable cannot be marked unavailable "
6138+
"with '@available' in script mode", ())
6139+
61366140
ERROR(availability_stored_property_no_potential,
61376141
none, "stored properties cannot be marked potentially unavailable with "
61386142
"'@available'", ())
61396143

6144+
ERROR(availability_stored_property_no_unavailable,
6145+
none, "stored properties cannot be marked unavailable with '@available'",
6146+
())
6147+
61406148
ERROR(availability_enum_element_no_potential,
61416149
none, "enum cases with associated values cannot be marked potentially unavailable with "
61426150
"'@available'", ())

lib/Sema/TypeCheckAttr.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4520,6 +4520,24 @@ TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
45204520
return diag::availability_deinit_no_unavailable;
45214521
}
45224522

4523+
if (auto *VD = dyn_cast<VarDecl>(D)) {
4524+
if (!VD->hasStorageOrWrapsStorage())
4525+
return None;
4526+
4527+
if (parentIsUnavailable(D))
4528+
return None;
4529+
4530+
// Do not permit unavailable script-mode global variables; their initializer
4531+
// expression is not lazily evaluated, so this would not be safe.
4532+
if (VD->isTopLevelGlobal())
4533+
return diag::availability_global_script_no_unavailable;
4534+
4535+
// Globals and statics are lazily initialized, so they are safe for
4536+
// unavailability.
4537+
if (!VD->isStatic() && !D->getDeclContext()->isModuleScopeContext())
4538+
return diag::availability_stored_property_no_unavailable;
4539+
}
4540+
45234541
return None;
45244542
}
45254543

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %target-typecheck-verify-swift -target %target-cpu-apple-macosx10.50
2+
3+
// REQUIRES: OS=macosx
4+
5+
struct AlwaysAvailable {}
6+
7+
@available(macOS, introduced: 10.50)
8+
struct Available10_50 {}
9+
10+
@available(macOS, introduced: 10.51)
11+
struct Available10_51 {}
12+
13+
@available(macOS, unavailable)
14+
struct UnavailableOnMacOS {}
15+
16+
@available(*, unavailable)
17+
struct UnavailableUnconditionally {}
18+
19+
var alwaysAvailableVar: AlwaysAvailable = .init() // Ok
20+
21+
@available(macOS, introduced: 10.50)
22+
var availableOn10_50Var: Available10_50 = .init() // Ok
23+
24+
// Script-mode globals have eagerly executed initializers so it isn't safe for
25+
// them to be unavailable.
26+
27+
@available(macOS, introduced: 10.51) // expected-error {{global variable cannot be marked potentially unavailable with '@available' in script mode}}
28+
var potentiallyUnavailableVar: Available10_51 = .init()
29+
30+
@available(macOS, unavailable) // expected-error {{global variable cannot be marked unavailable with '@available' in script mode}}
31+
var unavailableOnMacOSVar: UnavailableOnMacOS = .init()
32+
33+
@available(*, unavailable) // expected-error {{global variable cannot be marked unavailable with '@available' in script mode}}
34+
var unconditionallyUnavailableVar: UnavailableUnconditionally = .init()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %target-typecheck-verify-swift -parse-as-library
2+
3+
// REQUIRES: OS=macosx
4+
5+
@available(macOS, unavailable)
6+
struct UnavailableMacOSStruct {}
7+
8+
@available(*, unavailable)
9+
public struct UniversallyUnavailableStruct {}
10+
11+
// Ok, initialization of globals is lazy and boxed.
12+
@available(macOS, unavailable)
13+
var unavailableMacOSGlobal: UnavailableMacOSStruct = .init()
14+
15+
@available(*, unavailable)
16+
var universallyUnavailableGlobal: UniversallyUnavailableStruct = .init()
17+
18+
struct GoodAvailableStruct {
19+
// Ok, computed property.
20+
@available(macOS, unavailable)
21+
var unavailableMacOS: UnavailableMacOSStruct {
22+
get { UnavailableMacOSStruct() }
23+
}
24+
25+
// Ok, initialization of static vars is lazy and boxed.
26+
@available(macOS, unavailable)
27+
static var staticUnavailableMacOS: UnavailableMacOSStruct = .init()
28+
}
29+
30+
@available(macOS, unavailable)
31+
struct GoodUnavailableMacOSStruct {
32+
var unavailableMacOS: UnavailableMacOSStruct = .init()
33+
lazy var lazyUnavailableMacOS: UnavailableMacOSStruct = .init()
34+
35+
// Ok, the container is unavailable.
36+
@available(macOS, unavailable)
37+
var unavailableMacOSExplicit: UnavailableMacOSStruct = .init()
38+
}
39+
40+
@available(macOS, unavailable)
41+
struct GoodNestedUnavailableMacOSStruct {
42+
struct Inner {
43+
var unavailableMacOS: UnavailableMacOSStruct = .init()
44+
lazy var lazyUnavailableMacOS: UnavailableMacOSStruct = .init()
45+
46+
// Ok, the container is unavailable.
47+
@available(macOS, unavailable)
48+
var unavailableMacOSExplicit: UnavailableMacOSStruct = .init()
49+
}
50+
}
51+
52+
@available(*, unavailable)
53+
struct GoodUniversallyUnavailableStruct {
54+
var universallyUnavailable: UniversallyUnavailableStruct = .init()
55+
lazy var lazyUniversallyUnavailable: UniversallyUnavailableStruct = .init()
56+
57+
@available(*, unavailable)
58+
var universallyUnavailableExplicit: UniversallyUnavailableStruct = .init()
59+
}
60+
61+
struct BadStruct {
62+
// expected-error@+1 {{stored properties cannot be marked unavailable with '@available'}}
63+
@available(macOS, unavailable)
64+
var unavailableMacOS: UnavailableMacOSStruct = .init()
65+
66+
// expected-error@+1 {{stored properties cannot be marked unavailable with '@available'}}
67+
@available(macOS, unavailable)
68+
lazy var lazyUnavailableMacOS: UnavailableMacOSStruct = .init()
69+
70+
// expected-error@+1 {{stored properties cannot be marked unavailable with '@available'}}
71+
@available(*, unavailable)
72+
var universallyUnavailable: UniversallyUnavailableStruct = .init()
73+
}
74+
75+
enum GoodAvailableEnum {
76+
@available(macOS, unavailable)
77+
case unavailableMacOS(UnavailableMacOSStruct)
78+
}

test/Sema/availability_versions.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,6 @@ func functionAvailableOn10_51() {
5757
// expected-note@-1 {{add 'if #available' version check}}
5858
}
5959

60-
// Don't allow script-mode globals to marked potentially unavailable. Their
61-
// initializers are eagerly executed.
62-
@available(OSX, introduced: 10.51) // expected-error {{global variable cannot be marked potentially unavailable with '@available' in script mode}}
63-
var potentiallyUnavailableGlobalInScriptMode: Int = globalFuncAvailableOn10_51()
64-
6560
// Still allow other availability annotations on script-mode globals
6661
@available(OSX, deprecated: 10.51)
6762
var deprecatedGlobalInScriptMode: Int = 5

0 commit comments

Comments
 (0)