Skip to content

Commit 2a61f54

Browse files
committed
Sema: Avoid decoding unavailable enum elements in derived Codable conformances.
The compiler derived implementations of `Codable` conformances for enums did not take enum element unavailability into account. This could result in unavailable values being instantiated at runtime, leading to a general violation of the invariant that unavailable code is unreachable at runtime. This problem is possible because synthesized code is not type checked; had the conformances been hand-written, they would have been rejected for referencing unavailable declarations inside of available declarations. This change specifically alters derivation for the following declarations: - `Decodable.init(from:)` - `Encodable.encode(to:)` - `CodingKey.init(stringValue:)` Resolves rdar://110098469
1 parent c415744 commit 2a61f54

File tree

6 files changed

+133
-6
lines changed

6 files changed

+133
-6
lines changed

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ IDENTIFIER(CVarArg)
6363
IDENTIFIER(Cxx)
6464
IDENTIFIER(CxxStdlib)
6565
IDENTIFIER(Darwin)
66+
IDENTIFIER(dataCorrupted)
6667
IDENTIFIER(Distributed)
6768
IDENTIFIER(dealloc)
6869
IDENTIFIER(debugDescription)

lib/Sema/DerivedConformanceCodable.cpp

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,10 +1110,19 @@ deriveBodyEncodable_enum_encode(AbstractFunctionDecl *encodeDecl, void *) {
11101110
auto switchStmt = createEnumSwitch(
11111111
C, funcDC, enumRef, enumDecl, codingKeysEnum,
11121112
/*createSubpattern*/ true,
1113-
[&](auto *elt, auto *codingKeyCase,
1114-
auto payloadVars) -> std::tuple<EnumElementDecl *, BraceStmt *> {
1113+
[&](EnumElementDecl *elt, EnumElementDecl *codingKeyCase,
1114+
ArrayRef<VarDecl *> payloadVars)
1115+
-> std::tuple<EnumElementDecl *, BraceStmt *> {
11151116
SmallVector<ASTNode, 3> caseStatements;
11161117

1118+
if (elt->getAttrs().isUnavailable(C)) {
1119+
// This case is not encodable because it is unavailable and therefore
1120+
// should not be instantiable at runtime. Skipping this case will
1121+
// result in the SIL pipeline giving the switch a default case for
1122+
// unexpected values.
1123+
return std::make_tuple(nullptr, nullptr);
1124+
}
1125+
11171126
if (!codingKeyCase) {
11181127
// This case should not be encodable, so throw an error if an attempt
11191128
// is made to encode it
@@ -1539,11 +1548,13 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) {
15391548
// enum Foo : Codable {
15401549
// case bar(x: Int)
15411550
// case baz(y: String)
1551+
// @available(*, unavailable) case qux(z: Double)
15421552
//
15431553
// // Already derived by this point if possible.
15441554
// @derived enum CodingKeys : CodingKey {
15451555
// case bar
15461556
// case baz
1557+
// case qux
15471558
//
15481559
// @derived enum BarCodingKeys : CodingKey {
15491560
// case x
@@ -1552,6 +1563,10 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) {
15521563
// @derived enum BazCodingKeys : CodingKey {
15531564
// case y
15541565
// }
1566+
//
1567+
// @derived enum QuxCodingKeys : CodingKey {
1568+
// case z
1569+
// }
15551570
// }
15561571
//
15571572
// @derived init(from decoder: Decoder) throws {
@@ -1571,9 +1586,15 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) {
15711586
// self = .bar(x: x)
15721587
// case .baz:
15731588
// let nestedContainer = try container.nestedContainer(
1574-
// keyedBy: BarCodingKeys.self, forKey: .baz)
1589+
// keyedBy: BazCodingKeys.self, forKey: .baz)
15751590
// let y = try nestedContainer.decode(String.self, forKey: .y)
15761591
// self = .baz(y: y)
1592+
// case .qux:
1593+
// throw DecodingError.dataCorrupted(
1594+
// DecodingError.Context(
1595+
// codingPath: decoder.codingPath,
1596+
// debugDescription: "Unavailable enum element encountered.")
1597+
// )
15771598
// }
15781599
// }
15791600

@@ -1689,14 +1710,30 @@ deriveBodyDecodable_enum_init(AbstractFunctionDecl *initDecl, void *) {
16891710
auto switchStmt = createEnumSwitch(
16901711
C, funcDC, theKeyExpr, targetEnum, codingKeysEnum,
16911712
/*createSubpattern*/ false,
1692-
[&](auto *elt, auto *codingKeyCase,
1693-
auto payloadVars) -> std::tuple<EnumElementDecl *, BraceStmt *> {
1713+
[&](EnumElementDecl *elt, EnumElementDecl *codingKeyCase,
1714+
ArrayRef<VarDecl *> payloadVars)
1715+
-> std::tuple<EnumElementDecl *, BraceStmt *> {
16941716
// Skip this case if it's not defined in the CodingKeys
16951717
if (!codingKeyCase)
16961718
return std::make_tuple(nullptr, nullptr);
16971719

1698-
llvm::SmallVector<ASTNode, 3> caseStatements;
1720+
if (elt->getAttrs().isUnavailable(C)) {
1721+
// generate:
1722+
// throw DecodingError.dataCorrupted(
1723+
// DecodingError.Context(
1724+
// codingPath: decoder.codingPath,
1725+
// debugDescription: "...")
1726+
auto *throwStmt = createThrowCodingErrorStmt(
1727+
C, containerExpr, C.getDecodingErrorDecl(), C.Id_dataCorrupted,
1728+
llvm::None, "Unavailable enum element encountered.");
16991729

1730+
auto body =
1731+
BraceStmt::create(C, SourceLoc(), {throwStmt}, SourceLoc());
1732+
1733+
return std::make_tuple(codingKeyCase, body);
1734+
}
1735+
1736+
llvm::SmallVector<ASTNode, 3> caseStatements;
17001737
auto caseIdentifier = caseCodingKeysIdentifier(C, elt);
17011738
auto *caseCodingKeys =
17021739
lookupEvaluatedCodingKeysEnum(C, targetEnum, caseIdentifier);

lib/Sema/DerivedConformanceCodingKey.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ deriveBodyCodingKey_init_stringValue(AbstractFunctionDecl *initDecl, void *) {
276276
auto *selfRef = DerivedConformance::createSelfDeclRef(initDecl);
277277
SmallVector<ASTNode, 4> cases;
278278
for (auto *elt : elements) {
279+
// Skip the cases that would return unavailable elements since those can't
280+
// be instantiated at runtime.
281+
if (elt->getAttrs().isUnavailable(C))
282+
continue;
283+
279284
auto *litExpr = new (C) StringLiteralExpr(elt->getNameStr(), SourceRange(),
280285
/*Implicit=*/true);
281286
auto *litPat = ExprPattern::createImplicit(C, litExpr, /*DC*/ initDecl);

test/decl/protocol/special/coding/enum_codable_simple.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ enum SimpleEnum : Codable {
77
case b(z: String)
88
case c(Int, String, b: Bool)
99
case d(_ inner: Int)
10+
@available(*, unavailable) case e(Int)
1011

1112
// These lines have to be within the SimpleEnum type because CodingKeys
1213
// should be private.
@@ -17,6 +18,7 @@ enum SimpleEnum : Codable {
1718
let _ = SimpleEnum.BCodingKeys.self
1819
let _ = SimpleEnum.CCodingKeys.self
1920
let _ = SimpleEnum.DCodingKeys.self
21+
let _ = SimpleEnum.ECodingKeys.self
2022

2123
// The enum should have a case for each of the cases.
2224
let _ = SimpleEnum.CodingKeys.a
@@ -33,6 +35,8 @@ enum SimpleEnum : Codable {
3335
let _ = SimpleEnum.CCodingKeys.b
3436

3537
let _ = SimpleEnum.DCodingKeys._0
38+
39+
let _ = SimpleEnum.ECodingKeys._0
3640
}
3741
}
3842

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %target-run-simple-swift
2+
// RUN: %target-run-simple-swift(-unavailable-decl-optimization=complete)
3+
4+
// REQUIRES: executable_test
5+
6+
enum Key: CodingKey {
7+
case a
8+
@available(*, unavailable) case b
9+
}
10+
11+
guard Key(stringValue: "a") == .a else { fatalError() }
12+
guard Key(stringValue: "b") == nil else { fatalError() }
13+
14+
enum StringKey: String, CodingKey {
15+
case x
16+
@available(*, unavailable) case y
17+
}
18+
19+
guard StringKey(stringValue: "x") == .x else { fatalError() }
20+
guard StringKey(stringValue: "y") == nil else { fatalError() }
21+
guard StringKey(intValue: 0) == nil else { fatalError() }
22+
guard StringKey(intValue: 1) == nil else { fatalError() }
23+
24+
enum IntKey: Int, CodingKey {
25+
case zero = 0
26+
@available(*, unavailable) case one = 1
27+
}
28+
29+
guard IntKey(stringValue: "zero") == .zero else { fatalError() }
30+
guard IntKey(stringValue: "one") == nil else { fatalError() }
31+
guard IntKey(intValue: 0) == .zero else { fatalError() }
32+
guard IntKey(intValue: 1) == nil else { fatalError() }
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-run-simple-swift
2+
// RUN: %target-run-simple-swift(-unavailable-decl-optimization=complete)
3+
// REQUIRES: executable_test
4+
// REQUIRES: objc_interop
5+
6+
import StdlibUnittest
7+
import Foundation
8+
9+
var CodableEnumUnavailableElementTestSuite = TestSuite("CodableEnumUnavailableElement")
10+
11+
@available(*, unavailable)
12+
struct UnavailableStruct: Codable {}
13+
14+
enum EnumWithUnavailableCase: Codable {
15+
case a
16+
@available(*, unavailable)
17+
case b(UnavailableStruct)
18+
}
19+
20+
func decodeEnum(from json: String) throws -> EnumWithUnavailableCase {
21+
let data = json.data(using: .utf8)!
22+
return try JSONDecoder().decode(EnumWithUnavailableCase.self, from: data)
23+
}
24+
25+
CodableEnumUnavailableElementTestSuite.test("decode_available_case") {
26+
do {
27+
let decoded = try decodeEnum(from: #"{"a":{}}"#)
28+
switch decoded {
29+
case .a: break
30+
default: assertionFailure("Unexpected value \(decoded)")
31+
}
32+
} catch {
33+
expectUnreachable("Unexpected error \(error)")
34+
}
35+
}
36+
37+
CodableEnumUnavailableElementTestSuite.test("decode_unavailable_case") {
38+
do {
39+
let decoded = try decodeEnum(from: #"{"b":{"_0":{}}}"#)
40+
expectUnreachable("Unexpectedly decoded \(decoded)")
41+
} catch DecodingError.dataCorrupted {
42+
// Expected error
43+
} catch {
44+
expectUnreachable("Unexpected error \(error)")
45+
}
46+
}
47+
48+
runAllTests()

0 commit comments

Comments
 (0)