Skip to content

Commit 82abf97

Browse files
committed
[DefiniteInitialization] Fix the bug that allows overwriting immutable
variables of optional type (SR-7226, rdar://38624845)
1 parent cb3d6e2 commit 82abf97

File tree

3 files changed

+145
-11
lines changed

3 files changed

+145
-11
lines changed

lib/SILOptimizer/Mandatory/DIMemoryUseCollectorOwnership.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,16 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
950950
continue;
951951
}
952952

953+
// unchecked_take_enum_data_addr takes the address of the payload of the
954+
// optional, which could be used to update the payload. So, visit the
955+
// users of this instruction and ensure that there are no overwrites to an
956+
// immutable optional. Note that this special handling is for checking
957+
// immutability and is not for checking initialization before use.
958+
if (auto *enumDataAddr = dyn_cast<UncheckedTakeEnumDataAddrInst>(User)) {
959+
collectUses(enumDataAddr, BaseEltNo);
960+
continue;
961+
}
962+
953963
// We model destroy_addr as a release of the entire value.
954964
if (isa<DestroyAddrInst>(User)) {
955965
trackDestroy(User);

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,18 +1239,14 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
12391239
noteUninitializedMembers(Use);
12401240
return;
12411241
}
1242-
12431242

12441243
Diag<StringRef, bool> DiagMessage;
1245-
if (isa<MarkFunctionEscapeInst>(Inst)) {
1246-
if (Inst->getLoc().isASTNode<AbstractClosureExpr>())
1247-
DiagMessage = diag::variable_closure_use_uninit;
1248-
else
1249-
DiagMessage = diag::variable_function_use_uninit;
1250-
} else if (isa<UncheckedTakeEnumDataAddrInst>(Inst)) {
1251-
DiagMessage = diag::variable_used_before_initialized;
1252-
} else {
1244+
if (Inst->getLoc().isASTNode<AbstractClosureExpr>()) {
12531245
DiagMessage = diag::variable_closure_use_uninit;
1246+
} else if (isa<MarkFunctionEscapeInst>(Inst)) {
1247+
DiagMessage = diag::variable_function_use_uninit;
1248+
} else {
1249+
DiagMessage = diag::variable_used_before_initialized;
12541250
}
12551251

12561252
diagnoseInitError(Use, DiagMessage);
@@ -1708,10 +1704,11 @@ void LifetimeChecker::handleLoadUseFailure(const DIMemoryUse &Use,
17081704
// If this is a load into a promoted closure capture, diagnose properly as
17091705
// a capture.
17101706
if ((isa<LoadInst>(Inst) || isa<LoadBorrowInst>(Inst)) &&
1711-
Inst->getLoc().isASTNode<AbstractClosureExpr>())
1707+
Inst->getLoc().isASTNode<AbstractClosureExpr>()) {
17121708
diagnoseInitError(Use, diag::variable_closure_use_uninit);
1713-
else
1709+
} else {
17141710
diagnoseInitError(Use, diag::variable_used_before_initialized);
1711+
}
17151712
}
17161713

17171714
/// handleSelfInitUse - When processing a 'self' argument on a class, this is

test/SILOptimizer/definite_init_diagnostics.swift

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,3 +1355,130 @@ class ClassWithUnownedProperties {
13551355
c2 = tmp
13561356
}
13571357
}
1358+
1359+
// Tests for DI when optionals are defined using unchecked_take_enum_data_addr
1360+
// <rdar://38624845>
1361+
1362+
func testOptionalDoubleWrite() -> String? {
1363+
let sConst: String? // expected-note {{change 'let' to 'var' to make it mutable}}
1364+
sConst = ""
1365+
sConst? = "v2" // expected-error {{immutable value 'sConst' may only be initialized once}}
1366+
return sConst
1367+
}
1368+
1369+
func testOptionalDoubleWrite2() -> Int? {
1370+
let x: Int? // expected-note {{change 'let' to 'var' to make it mutable}}
1371+
x = 0
1372+
x? = 0 // expected-error {{immutable value 'x' may only be initialized once}}
1373+
return x
1374+
}
1375+
1376+
protocol DIOptionalTestProtocol {
1377+
var f: Int { get set }
1378+
}
1379+
1380+
func testOptionalDoubleWrite3(p1: DIOptionalTestProtocol) -> DIOptionalTestProtocol? {
1381+
let x: DIOptionalTestProtocol? // expected-note {{change 'let' to 'var' to make it mutable}}
1382+
x = p1
1383+
x? = p1 // expected-error {{immutable value 'x' may only be initialized once}}
1384+
return x
1385+
}
1386+
1387+
func testOptionalWrite() {
1388+
let x: Int? // expected-note {{constant defined here}}
1389+
// expected-warning@-1 {{immutable value 'x' was never used; consider removing it}}
1390+
x? = 0 // expected-error {{constant 'x' used before being initialized}}
1391+
}
1392+
1393+
func testOptionalWriteGenerics<T>(p: T) -> T? {
1394+
let x: T? // expected-note {{constant defined here}}
1395+
// expected-note@-1 {{change 'let' to 'var' to make it mutable}}
1396+
x? = p // expected-error {{constant 'x' used before being initialized}}
1397+
x = p // expected-error {{immutable value 'x' may only be initialized once}}
1398+
return x
1399+
}
1400+
1401+
func testOptionalWriteGenerics2<T>(p: T) -> T? {
1402+
let x: T? // expected-note {{change 'let' to 'var' to make it mutable}}
1403+
x = p
1404+
x? = p // expected-error {{immutable value 'x' may only be initialized once}}
1405+
return x
1406+
}
1407+
1408+
enum TestOptionalEnum {
1409+
case Cons(Int)
1410+
case Nil()
1411+
}
1412+
1413+
func testOptionalWithEnum(p: TestOptionalEnum) -> TestOptionalEnum? {
1414+
let x: TestOptionalEnum? // expected-note {{change 'let' to 'var' to make it mutable}}
1415+
x = p
1416+
x? = p // expected-error {{immutable value 'x' may only be initialized once}}
1417+
return x
1418+
}
1419+
1420+
// Tests for optional chaining
1421+
1422+
class DIOptionalTestClass {
1423+
var r: DIOptionalTestClass? = nil
1424+
var f: Int = 0;
1425+
let g: Int = 0;
1426+
}
1427+
1428+
func testOptionalChaining(p: DIOptionalTestClass?) {
1429+
p?.f = 2
1430+
}
1431+
1432+
func testOptionalChaining2(p: DIOptionalTestClass?) -> DIOptionalTestClass? {
1433+
let x: DIOptionalTestClass?
1434+
x = p
1435+
x?.f = 1
1436+
p?.r?.f = 2
1437+
return x
1438+
}
1439+
1440+
struct DIOptionalTestStruct {
1441+
var f: Int
1442+
}
1443+
1444+
func testOptionalChaining3() -> DIOptionalTestStruct? {
1445+
let x: DIOptionalTestStruct? // expected-note {{change 'let' to 'var' to make it mutable}}
1446+
x = DIOptionalTestStruct(f: 0)
1447+
x?.f = 2 // expected-error {{immutable value 'x' may only be initialized once}}
1448+
return x
1449+
}
1450+
1451+
extension DIOptionalTestStruct {
1452+
public init?() {
1453+
self.f = 0
1454+
}
1455+
}
1456+
1457+
func testOptionalChaining4() -> DIOptionalTestStruct? {
1458+
let x: DIOptionalTestStruct? // expected-note {{change 'let' to 'var' to make it mutable}}
1459+
x = DIOptionalTestStruct()
1460+
x?.f = 2 // expected-error {{immutable value 'x' may only be initialized once}}
1461+
return x
1462+
}
1463+
1464+
struct DIOptionalTestStructPair {
1465+
var pair: (Int, Int)
1466+
}
1467+
1468+
func test6() -> DIOptionalTestStructPair? {
1469+
let x: DIOptionalTestStructPair? // expected-note {{change 'let' to 'var' to make it mutable}}
1470+
x = DIOptionalTestStructPair(pair: (0, 0))
1471+
x?.pair.0 = 1 // expected-error {{immutable value 'x' may only be initialized once}}
1472+
return x
1473+
}
1474+
1475+
func testOptionalChainingWithGenerics<T: DIOptionalTestProtocol>(p: T) -> T? {
1476+
let x: T? // expected-note {{constant defined here}}
1477+
// expected-note@-1 {{constant defined here}}
1478+
// expected-note@-2 {{constant defined here}}
1479+
1480+
// note that here assignment to 'f' is a call to the setter.
1481+
x?.f = 0 // expected-error {{constant 'x' used before being initialized}}
1482+
// expected-error@-1 {{constant 'x' passed by reference before being initialized}}
1483+
return x // expected-error {{constant 'x' used before being initialized}}
1484+
}

0 commit comments

Comments
 (0)