Skip to content

Commit 7f22ad6

Browse files
authored
Merge pull request #64835 from tshortli/ban-unavailable-stored-properties-5.9
[5.9] Sema: Ban unavailable stored properties
2 parents ed0e165 + b81b05b commit 7f22ad6

21 files changed

+235
-108
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

test/ClangImporter/Inputs/frameworks/SPIContainer.framework/Headers/SPIContainer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212

1313
SPI_AVAILABLE(macos(10.7))
1414
@interface SPIInterface1
15+
- (instancetype)init;
1516
@end
1617

1718
__SPI_AVAILABLE(macos(10.7))
1819
@interface SPIInterface2
20+
- (instancetype)init;
1921
@end
2022

2123
@interface SharedInterface

test/ClangImporter/availability_spi_as_unavailable.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
// REQUIRES: OS=macosx
2-
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -verify -DNOT_UNDERLYING -library-level api
3-
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -module-name SPIContainer -import-underlying-module -verify -library-level api
2+
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -verify -DNOT_UNDERLYING -library-level api -parse-as-library -require-explicit-availability=ignore
3+
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -module-name SPIContainer -import-underlying-module -verify -library-level api -parse-as-library -require-explicit-availability=ignore
44

55
#if NOT_UNDERLYING
66
import SPIContainer
77
#endif
88

9-
@_spi(a) public let a: SPIInterface1
10-
@_spi(a) public let b: SPIInterface2
9+
@_spi(a) public let a: SPIInterface1 = .init()
10+
@_spi(a) public let b: SPIInterface2 = .init()
1111

12-
public let c: SPIInterface1 // expected-error{{cannot use class 'SPIInterface1' here; it is an SPI imported from 'SPIContainer'}}
13-
public let d: SPIInterface2 // expected-error{{cannot use class 'SPIInterface2' here; it is an SPI imported from 'SPIContainer'}}
12+
public let c: SPIInterface1 = .init() // expected-error{{cannot use class 'SPIInterface1' here; it is an SPI imported from 'SPIContainer'}}
13+
public let d: SPIInterface2 = .init() // expected-error{{cannot use class 'SPIInterface2' here; it is an SPI imported from 'SPIContainer'}}
1414

1515
@inlinable
16-
public func inlinableUsingSPI() { // expected-warning{{public declarations should have an availability attribute with an introduction version}}
16+
public func inlinableUsingSPI() {
1717
SharedInterface.foo() // expected-error{{class method 'foo()' cannot be used in an '@inlinable' function because it is an SPI imported from 'SPIContainer'}}
1818
}
1919

2020
@available(macOS, unavailable)
21-
public let e: SPIInterface2
21+
public let e: SPIInterface2 = .init()
2222

2323
@available(iOS, unavailable)
24-
public let f: SPIInterface2 // expected-error{{cannot use class 'SPIInterface2' here; it is an SPI imported from 'SPIContainer'}}
24+
public let f: SPIInterface2 = .init() // expected-error{{cannot use class 'SPIInterface2' here; it is an SPI imported from 'SPIContainer'}}
2525

2626
@inlinable
2727
@available(macOS, unavailable)

test/ClangImporter/availability_spi_library_level_spi.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
// REQUIRES: OS=macosx
22

3-
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -verify -DNOT_UNDERLYING
4-
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -verify -DNOT_UNDERLYING -library-level spi
3+
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -verify -DNOT_UNDERLYING -parse-as-library -require-explicit-availability=ignore
4+
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -verify -DNOT_UNDERLYING -library-level spi -parse-as-library -require-explicit-availability=ignore
55

6-
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -module-name SPIContainer -import-underlying-module -verify
7-
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -module-name SPIContainer -import-underlying-module -verify -library-level spi
6+
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -module-name SPIContainer -import-underlying-module -verify -parse-as-library -require-explicit-availability=ignore
7+
// RUN: %target-swift-frontend -typecheck %s -F %S/Inputs/frameworks -module-name SPIContainer -import-underlying-module -verify -library-level spi -parse-as-library -require-explicit-availability=ignore
88

99

1010
#if NOT_UNDERLYING
1111
import SPIContainer
1212
#endif
1313

14-
@_spi(a) public let a: SPIInterface1
15-
@_spi(a) public let b: SPIInterface2
14+
@_spi(a) public let a: SPIInterface1 = .init()
15+
@_spi(a) public let b: SPIInterface2 = .init()
1616

17-
public let c: SPIInterface1
18-
public let d: SPIInterface2
17+
public let c: SPIInterface1 = .init()
18+
public let d: SPIInterface2 = .init()
1919

2020
@inlinable
2121
public func inlinableUsingSPI() {
2222
SharedInterface.foo()
2323
}
2424

2525
@available(macOS, unavailable)
26-
public let e: SPIInterface2
26+
public let e: SPIInterface2 = .init()
2727

2828
@available(iOS, unavailable)
29-
public let f: SPIInterface2
29+
public let f: SPIInterface2 = .init()
3030

3131
@inlinable
3232
@available(macOS, unavailable)

test/IRGen/float16_macos.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
// REQUIRES: CPU=x86_64
66
// UNSUPPORTED: use_os_stdlib
77

8-
@available(macOS 11, *)
9-
public struct Float16Wrapper {
10-
@available(macOS, unavailable)
11-
var x: Float16
8+
@inline(never)
9+
func blackHole<T>(_ t: T.Type) {}
10+
11+
@available(macOS, unavailable)
12+
public func useFloat16() {
13+
blackHole(Float16.self)
1214
}
1315

14-
// CHECK-LABEL: @"$ss7Float16VMn" = extern_weak global %swift.type_descriptor
16+
// CHECK-LABEL: @"$ss7Float16VN" = extern_weak global %swift.type

test/IRGen/unavailable_decl_optimization_class.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ public class AvailableClass<T> {
1010
// CHECK-STRIP-NOT: s4Test14AvailableClassC19unavailablePropertyxvs
1111
// CHECK-STRIP-NOT: s4Test14AvailableClassC19unavailablePropertyxvM
1212
@available(*, unavailable)
13-
public var unavailableProperty: T
13+
public var unavailableProperty: T {
14+
get { fatalError() }
15+
set { fatalError() }
16+
_modify { fatalError() }
17+
}
1418

1519
// CHECK-NO-STRIP: s4Test14AvailableClassCyACyxGxcfC
1620
// CHECK-NO-STRIP: s4Test14AvailableClassCyACyxGxcfc

test/IRGen/unavailable_decl_optimization_struct.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ public struct AvailableStruct<T> {
1515
// CHECK-STRIP-NOT: s4Test15AvailableStructV19unavailablePropertyxvs
1616
// CHECK-STRIP-NOT: s4Test15AvailableStructV19unavailablePropertyxvM
1717
@available(*, unavailable)
18-
public var unavailableProperty: T
18+
public var unavailableProperty: T {
19+
get { fatalError() }
20+
set { fatalError() }
21+
_modify { fatalError() }
22+
}
1923

2024
// CHECK-NO-STRIP: s4Test15AvailableStructVyACyxGxcfC
2125
// CHECK-STRIP-NOT: s4Test15AvailableStructVyACyxGxcfC

test/Migrator/double_fixit_ok.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: not %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t/double_fixit_ok.result -swift-version 4
2+
// RUN: not %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t/double_fixit_ok.result -swift-version 4 -parse-as-library
33
// RUN: %diff -u %s.expected %t/double_fixit_ok.result
4-
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 5
4+
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 5 -parse-as-library
55

66
@available(swift, obsoleted: 4, renamed: "Thing.constant___renamed")
77
let ThingConstantGotRenamed = 1

test/Migrator/double_fixit_ok.swift.expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: not %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t/double_fixit_ok.result -swift-version 4
2+
// RUN: not %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t/double_fixit_ok.result -swift-version 4 -parse-as-library
33
// RUN: %diff -u %s.expected %t/double_fixit_ok.result
4-
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 5
4+
// RUN: %target-swift-frontend -typecheck %s.expected -swift-version 5 -parse-as-library
55

66
@available(swift, obsoleted: 4, renamed: "Thing.constant___renamed")
77
let ThingConstantGotRenamed = 1

test/SILGen/unavailable_decl_optimization.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ public struct S<T> {
8888
// CHECK-STRIP-NOT: s4Test1SV19unavailablePropertyxvs
8989
// CHECK-STRIP-NOT: s4Test1SV19unavailablePropertyxvM
9090
@available(*, unavailable)
91-
public var unavailableProperty: T
91+
public var unavailableProperty: T {
92+
get { fatalError() }
93+
set { fatalError() }
94+
_modify { fatalError() }
95+
}
9296

9397
// CHECK-NO-STRIP: s4Test1SVyACyxGxcfC
9498
// CHECK-STRIP-NOT: s4Test1SVyACyxGxcfC
@@ -128,7 +132,11 @@ public class C<T> {
128132
// CHECK-STRIP-NOT: s4Test1CC19unavailablePropertyxvs
129133
// CHECK-STRIP-NOT: s4Test1CC19unavailablePropertyxvM
130134
@available(*, unavailable)
131-
public var unavailableProperty: T
135+
public var unavailableProperty: T {
136+
get { fatalError() }
137+
set { fatalError() }
138+
_modify { fatalError() }
139+
}
132140

133141
// CHECK-NO-STRIP: s4Test1CCyACyxGxcfC
134142
// CHECK-NO-STRIP: s4Test1CCyACyxGxcfc

test/Sema/availability.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift -module-name MyModule
1+
// RUN: %target-typecheck-verify-swift -parse-as-library -module-name MyModule
22

33
// REQUIRES: OS=macosx
44

@@ -175,7 +175,10 @@ func testPlatforms() {
175175

176176
struct VarToFunc {
177177
@available(*, unavailable, renamed: "function()")
178-
var variable: Int // expected-note 2 {{explicitly marked unavailable here}}
178+
var variable: Int { // expected-note 2 {{explicitly marked unavailable here}}
179+
get { 0 }
180+
set {}
181+
}
179182

180183
@available(*, unavailable, renamed: "function()")
181184
func oldFunction() -> Int { return 42 } // expected-note 2 {{explicitly marked unavailable here}}
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)