Skip to content

Commit 24a3317

Browse files
authored
Merge pull request #64885 from tshortli/ban-unavailable-stored-properties
Sema: Ban unavailable stored properties
2 parents 3e3a98f + 9812c90 commit 24a3317

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
@@ -4521,6 +4521,24 @@ TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
45214521
return diag::availability_deinit_no_unavailable;
45224522
}
45234523

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

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

0 commit comments

Comments
 (0)