From f57a6cfd6dd07d2aa4ed6e4f0c01a5f6f0d11bd6 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Fri, 15 May 2020 13:29:09 -0700 Subject: [PATCH] [silgen] Teach CleanupCloner how to handle OwnedValueWritebackCleanups correctly. Previously we would just forward the cleanup and create a normal "destroy" cleanup resulting in early deallocation and use after frees along error paths. As part of this I also had to tweak how DI recognizes self init writebacks to not use SILLocations. This is approach is more robust (since we aren't relying on SourceLocs/have verifiers to make sure we don't violate SIL) and also avoids issues from the write back store not necessarily have the same SILLocation. --- lib/SILGen/Cleanup.cpp | 34 +- lib/SILGen/Cleanup.h | 39 +- lib/SILGen/SILGenExpr.cpp | 6 + lib/SILGen/SILGenFunction.h | 2 + .../Mandatory/DIMemoryUseCollector.cpp | 31 +- .../Mandatory/DefiniteInitialization.cpp | 1 + ..._composed_with_throwing_initializers.swift | 678 ++++++++++++++++++ .../objc_throwing_initializers.swift | 10 + ..._init_markuninitialized_delegatingself.sil | 49 +- 9 files changed, 839 insertions(+), 11 deletions(-) create mode 100644 test/Interpreter/objc_failable_composed_with_throwing_initializers.swift diff --git a/lib/SILGen/Cleanup.cpp b/lib/SILGen/Cleanup.cpp index e92f5dbd443ce..20eb0cc3c8e7e 100644 --- a/lib/SILGen/Cleanup.cpp +++ b/lib/SILGen/Cleanup.cpp @@ -220,6 +220,21 @@ void CleanupManager::setCleanupState(CleanupsDepth depth, CleanupState state) { popTopDeadCleanups(); } +std::tuple> +CleanupManager::getFlagsAndWritebackBuffer(CleanupHandle depth) { + auto iter = stack.find(depth); + assert(iter != stack.end() && "can't change end of cleanups stack"); + assert(iter->getState() != CleanupState::Dead && + "Trying to get writeback buffer of a dead cleanup?!"); + + auto resultFlags = iter->getFlags(); + Optional result; + bool foundValue = iter->getWritebackBuffer([&](SILValue v) { result = v; }); + (void)foundValue; + assert(result.hasValue() == foundValue); + return std::make_tuple(resultFlags, result); +} + void CleanupManager::forwardCleanup(CleanupsDepth handle) { auto iter = stack.find(handle); assert(iter != stack.end() && "can't change end of cleanups stack"); @@ -344,7 +359,16 @@ void CleanupStateRestorationScope::pop() && { popImpl(); } //===----------------------------------------------------------------------===// CleanupCloner::CleanupCloner(SILGenFunction &SGF, const ManagedValue &mv) - : SGF(SGF), hasCleanup(mv.hasCleanup()), isLValue(mv.isLValue()) {} + : SGF(SGF), hasCleanup(mv.hasCleanup()), isLValue(mv.isLValue()), + writebackBuffer(None) { + if (hasCleanup) { + auto handle = mv.getCleanup(); + auto state = SGF.Cleanups.getFlagsAndWritebackBuffer(handle); + if (SILValue value = std::get<1>(state).getValueOr(SILValue())) { + writebackBuffer = value; + } + } +} CleanupCloner::CleanupCloner(SILGenBuilder &builder, const ManagedValue &mv) : CleanupCloner(builder.getSILGenFunction(), mv) {} @@ -372,6 +396,14 @@ ManagedValue CleanupCloner::clone(SILValue value) const { return ManagedValue::forUnmanaged(value); } + if (writebackBuffer.hasValue()) { + auto loc = RegularLocation::getAutoGeneratedLocation(); + auto cleanup = + SGF.enterOwnedValueWritebackCleanup(loc, *writebackBuffer, value); + return ManagedValue::forExclusivelyBorrowedOwnedObjectRValue(value, + cleanup); + } + if (value->getType().isAddress()) { return SGF.emitManagedBufferWithCleanup(value); } diff --git a/lib/SILGen/Cleanup.h b/lib/SILGen/Cleanup.h index 175b2cc6bb31a..cf8b98df606e3 100644 --- a/lib/SILGen/Cleanup.h +++ b/lib/SILGen/Cleanup.h @@ -19,6 +19,7 @@ #include "swift/Basic/DiverseStack.h" #include "swift/SIL/SILLocation.h" +#include "swift/SIL/SILValue.h" #include "llvm/ADT/SmallVector.h" namespace swift { @@ -75,10 +76,23 @@ llvm::raw_ostream &operator<<(raw_ostream &os, CleanupState state); class LLVM_LIBRARY_VISIBILITY Cleanup { friend class CleanupManager; + friend class CleanupCloner; - unsigned allocatedSize; +protected: + // A set of flags that categorize the type of cleanup such that it can be + // recreated via SILGenFunction methods based on the type of argument input. + // + // Example: Distinguishing in between @owned cleanups with a writeback buffer + // (ExclusiveBorrowCleanup) or ones that involve formal access cleanups. + enum class Flags : uint8_t { + None = 0, + ExclusiveBorrowCleanup = 1, + }; +private: CleanupState state; + unsigned allocatedSize : 24; + Flags flags : 8; protected: Cleanup() {} @@ -99,6 +113,16 @@ class LLVM_LIBRARY_VISIBILITY Cleanup { virtual void emit(SILGenFunction &SGF, CleanupLocation loc, ForUnwind_t forUnwind) = 0; virtual void dump(SILGenFunction &SGF) const = 0; + +protected: + Flags getFlags() const { return flags; } + + /// Call func passing in the SILValue address that this cleanup will write + /// back to if supported and any flags associated with the cleanup. Returns + /// false otherwise. + virtual bool getWritebackBuffer(function_ref func) { + return false; + } }; /// A cleanup depth is generally used to denote the set of cleanups @@ -117,6 +141,7 @@ typedef DiverseStackImpl::stable_iterator CleanupHandle; class LLVM_LIBRARY_VISIBILITY CleanupManager { friend class Scope; + friend class CleanupCloner; SILGenFunction &SGF; @@ -229,7 +254,7 @@ class LLVM_LIBRARY_VISIBILITY CleanupManager { /// Set the state of the cleanup at the given depth. /// The transition must be non-trivial and legal. void setCleanupState(CleanupHandle depth, CleanupState state); - + /// True if there are any active cleanups in the scope between the two /// cleanup handles. bool hasAnyActiveCleanups(CleanupsDepth from, CleanupsDepth to); @@ -246,6 +271,12 @@ class LLVM_LIBRARY_VISIBILITY CleanupManager { /// Verify that the given cleanup handle is valid. void checkIterator(CleanupHandle handle) const; + +private: + // Look up the flags and optionally the writeback address associated with the + // cleanup at \p depth. If + std::tuple> + getFlagsAndWritebackBuffer(CleanupHandle depth); }; /// An RAII object that allows the state of a cleanup to be @@ -274,10 +305,14 @@ class CleanupStateRestorationScope { void popImpl(); }; +/// Extract enough information from a managed value to reliably clone its +/// cleanup (if it has any) on a newly computed type. This includes modeling +/// writeback buffers. class CleanupCloner { SILGenFunction &SGF; bool hasCleanup; bool isLValue; + Optional writebackBuffer; public: CleanupCloner(SILGenFunction &SGF, const ManagedValue &mv); diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 58f6c3396929d..a64ad886888f0 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -722,6 +722,7 @@ namespace { /// cleanup to take ownership of the value and thus prevent it form being /// written back. struct OwnedValueWritebackCleanup final : Cleanup { + using Flags = Cleanup::Flags; /// We store our own loc so that we can ensure that DI ignores our writeback. SILLocation loc; @@ -733,6 +734,11 @@ struct OwnedValueWritebackCleanup final : Cleanup { SILValue value) : loc(loc), lvalueAddress(lvalueAddress), value(value) {} + bool getWritebackBuffer(function_ref func) override { + func(lvalueAddress); + return true; + } + void emit(SILGenFunction &SGF, CleanupLocation l, ForUnwind_t forUnwind) override { SILValue valueToStore = value; SILType lvalueObjTy = lvalueAddress->getType().getObjectType(); diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 854fa87b50c0e..ca922d32de6bf 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1188,6 +1188,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction CleanupHandle enterDeallocateUninitializedArrayCleanup(SILValue array); void emitUninitializedArrayDeallocation(SILLocation loc, SILValue array); + /// Emit a cleanup for an owned value that should be written back at end of + /// scope if the value is not forwarded. CleanupHandle enterOwnedValueWritebackCleanup(SILLocation loc, SILValue address, SILValue newValue); diff --git a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp index 8bbb9d30ed1aa..b83872daea424 100644 --- a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp +++ b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp @@ -1639,6 +1639,7 @@ void ClassInitElementUseCollector::collectClassInitSelfUses() { } // A store of a load from the box is ignored. + // // SILGen emits these if delegation to another initializer was // interrupted before the initializer was called. SILValue src = SI->getSrc(); @@ -1778,14 +1779,30 @@ void ClassInitElementUseCollector::collectClassInitSelfLoadUses( } } - // If this load's value is being stored back into the delegating - // mark_uninitialized buffer and it is a self init use, skip the - // use. This is to handle situations where due to usage of a metatype to - // allocate, we do not actually consume self. + // If this load's value is being stored immediately back into the delegating + // mark_uninitialized buffer, skip the use. + // + // This is to handle situations where we do not actually consume self as a + // result of situations such as: + // + // 1. The usage of a metatype to allocate the object. + // + // 2. If our self init call has a throwing function as an argument that + // actually throws. if (auto *SI = dyn_cast(User)) { - if (SI->getDest() == MUI && - (isSelfInitUse(User) || isSuperInitUse(User))) { - continue; + if (SI->getDest() == MUI) { + SILValue src = SI->getSrc(); + + // Look through conversions. + while (auto *conversion = dyn_cast(src)) { + src = conversion->getConverted(); + } + + if (auto *li = dyn_cast(src)) { + if (li->getOperand() == MUI) { + continue; + } + } } } diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index 8250a4eba1993..65e5513dc1d7e 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -785,6 +785,7 @@ void LifetimeChecker::doIt() { break; case DIUseKind::LoadForTypeOfSelf: handleLoadForTypeOfSelfUse(Use); + break; } } diff --git a/test/Interpreter/objc_failable_composed_with_throwing_initializers.swift b/test/Interpreter/objc_failable_composed_with_throwing_initializers.swift new file mode 100644 index 0000000000000..9a365a5b687f6 --- /dev/null +++ b/test/Interpreter/objc_failable_composed_with_throwing_initializers.swift @@ -0,0 +1,678 @@ +// RUN: %empty-directory(%t) +// +// RUN: %target-clang -fobjc-arc %S/Inputs/ObjCClasses/ObjCClasses.m -c -o %t/ObjCClasses.o +// RUN: %target-build-swift -I %S/Inputs/ObjCClasses/ -Xlinker %t/ObjCClasses.o %s -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out + +// RUN: %empty-directory(%t) +// +// target-build-swift assumes we want -swift-version 4. Behavior in initializers +// changed in swift 5, so we want to explicitly check it as well. +// +// RUN: %target-clang -fobjc-arc %S/Inputs/ObjCClasses/ObjCClasses.m -c -o %t/ObjCClasses.o +// RUN: %target-build-swift -I %S/Inputs/ObjCClasses/ -Xlinker %t/ObjCClasses.o %s -o %t/a.out -swift-version 5 +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out + +// REQUIRES: executable_test +// REQUIRES: objc_interop + +// These tests are failable tests that compose with throwing initializers. They +// all catch the throw on failure and return None. + +import Foundation +import ObjCClasses +import StdlibUnittest + +var FailableComposedWithThrowingInitTestSuite = TestSuite("FailableInitObjCComposedWithThrowingInits") +defer { runAllTests() } + +enum E : Error { + case X +} + +func maybeThrow(_ shouldThrow: Bool, _ result: Int? = nil) throws -> Int { + if shouldThrow { + throw E.X + } + return result ?? 0 +} + +func mustFail(f: () -> T?) { + if f() != nil { + preconditionFailure("Didn't fail") + } +} + +class Bear : NSLifetimeTracked { + let x: LifetimeTracked + + /* Designated */ + init(n: Int) { + x = LifetimeTracked(0) + } + + init?(n: Int, before: Bool) { + let v: Int + do { + v = try maybeThrow(before) + } catch { + return nil + } + self.x = LifetimeTracked(v) + } + + init?(n: Int, after: Bool) { + self.x = LifetimeTracked(0) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + init?(n: Int, before: Bool, after: Bool) { + let v: Int + do { + v = try maybeThrow(before) + } catch { + return nil + } + self.x = LifetimeTracked(v) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + /* Convenience */ + convenience init?(before: Bool) { + let v: Int + do { + v = try maybeThrow(before) + } catch { + return nil + } + self.init(n: v) + } + + convenience init?(during: Bool) { + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + convenience init?(before: Bool, during: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + convenience init?(after: Bool) { + self.init(n: 0) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + convenience init?(before: Bool, after: Bool) { + let value: Int + do { + value = try maybeThrow(before) + } catch { + return nil + } + self.init(n: value) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + convenience init?(during: Bool, after: Bool) { + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + convenience init?(before: Bool, during: Bool, after: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + /* Exotic */ + convenience init!(IUO: Bool) { + self.init(before: IUO) + } + + convenience init(force: Bool) { + self.init(before: force)! + } +} + +class PolarBear : Bear { + let y: LifetimeTracked + + /* Designated */ + override init(n: Int) { + self.y = LifetimeTracked(0) + super.init(n: n) + } + + override init?(n: Int, before: Bool) { + let value: Int + do { + value = try maybeThrow(before) + } catch { + return nil + } + self.y = LifetimeTracked(0) + super.init(n: value) + } + + init?(n: Int, during: Bool) { + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + init?(n: Int, before: Bool, during: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + override init?(n: Int, after: Bool) { + self.y = LifetimeTracked(0) + super.init(n: n) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + init?(n: Int, during: Bool, after: Bool) { + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + override init?(n: Int, before: Bool, after: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + self.y = LifetimeTracked(0) + super.init(n: n) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + init?(n: Int, before: Bool, during: Bool, after: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } +} + +class GuineaPig : Bear { + let y: LifetimeTracked + let t: T + + init?(t: T, during: Bool) { + self.y = LifetimeTracked(0) + self.t = t + super.init(n: 0, before: during) + } +} + +FailableComposedWithThrowingInitTestSuite.test("FailableInitFailure_Root") { + mustFail { Bear(n: 0, before: true) } + mustFail { Bear(n: 0, after: true) } + mustFail { Bear(n: 0, before: true, after: false) } + mustFail { Bear(n: 0, before: false, after: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("FailableInitFailure_Derived") { + mustFail { PolarBear(n: 0, before: true) } + mustFail { PolarBear(n: 0, during: true) } + mustFail { PolarBear(n: 0, before: true, during: false) } + mustFail { PolarBear(n: 0, before: false, during: true) } + mustFail { PolarBear(n: 0, after: true) } + mustFail { PolarBear(n: 0, during: true, after: false) } + mustFail { PolarBear(n: 0, during: false, after: true) } + mustFail { PolarBear(n: 0, before: true, after: false) } + mustFail { PolarBear(n: 0, before: false, after: true) } + mustFail { PolarBear(n: 0, before: true, during: false, after: false) } + mustFail { PolarBear(n: 0, before: false, during: true, after: false) } + mustFail { PolarBear(n: 0, before: false, during: false, after: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("DesignatedInitFailure_DerivedGeneric") { + mustFail { GuineaPig(t: LifetimeTracked(0), during: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("ConvenienceInitFailure_Root") { + mustFail { Bear(before: true) } + mustFail { Bear(during: true) } + mustFail { Bear(before: true, during: false) } + mustFail { Bear(before: false, during: true) } + mustFail { Bear(after: true) } + mustFail { Bear(before: true, after: false) } + mustFail { Bear(before: false, after: true) } + mustFail { Bear(during: true, after: false) } + mustFail { Bear(during: false, after: true) } + mustFail { Bear(before: true, during: false, after: false) } + mustFail { Bear(before: false, during: true, after: false) } + mustFail { Bear(before: false, during: false, after: true) } + + _ = Bear(IUO: false) + _ = Bear(force: false) + + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("ConvenienceInitFailure_Derived") { + mustFail { PolarBear(before: true) } + mustFail { PolarBear(during: true) } + mustFail { PolarBear(before: true, during: false) } + mustFail { PolarBear(before: false, during: true) } + mustFail { PolarBear(after: true) } + mustFail { PolarBear(before: true, after: false) } + mustFail { PolarBear(before: false, after: true) } + mustFail { PolarBear(during: true, after: false) } + mustFail { PolarBear(during: false, after: true) } + mustFail { PolarBear(before: true, during: false, after: false) } + mustFail { PolarBear(before: false, during: true, after: false) } + mustFail { PolarBear(before: false, during: false, after: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} + +// @objc + +class AtObjCBear : NSLifetimeTracked { + let x: LifetimeTracked + + /* Designated */ + @objc init(n: Int) { + x = LifetimeTracked(0) + } + + @objc init?(n: Int, before: Bool) { + let v: Int + do { + v = try maybeThrow(before) + } catch { + return nil + } + self.x = LifetimeTracked(v) + } + + @objc init?(n: Int, after: Bool) { + self.x = LifetimeTracked(0) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc init?(n: Int, before: Bool, after: Bool) { + let v: Int + do { + v = try maybeThrow(before) + } catch { + return nil + } + self.x = LifetimeTracked(v) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + /* Convenience */ + @objc convenience init?(before: Bool) { + let v: Int + do { + v = try maybeThrow(before) + } catch { + return nil + } + self.init(n: v) + } + + @objc convenience init?(during: Bool) { + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + @objc convenience init?(before: Bool, during: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + @objc convenience init?(after: Bool) { + self.init(n: 0) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc convenience init?(before: Bool, after: Bool) { + let value: Int + do { + value = try maybeThrow(before) + } catch { + return nil + } + self.init(n: value) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc convenience init?(during: Bool, after: Bool) { + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc convenience init?(before: Bool, during: Bool, after: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + + do { + try self.init(n: maybeThrow(during)) + } catch { + return nil + } + + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + /* Exotic */ + @objc convenience init!(IUO: Bool) { + self.init(before: IUO) + } + + @objc convenience init(force: Bool) { + self.init(before: force)! + } +} + +class AtObjCPolarBear : AtObjCBear { + let y: LifetimeTracked + + /* Designated */ + @objc override init(n: Int) { + self.y = LifetimeTracked(0) + super.init(n: n) + } + + @objc override init?(n: Int, before: Bool) { + let value: Int + do { + value = try maybeThrow(before) + } catch { + return nil + } + self.y = LifetimeTracked(0) + super.init(n: value) + } + + @objc init?(n: Int, during: Bool) { + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + @objc init?(n: Int, before: Bool, during: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + } + + @objc override init?(n: Int, after: Bool) { + self.y = LifetimeTracked(0) + super.init(n: n) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc init?(n: Int, during: Bool, after: Bool) { + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc override init?(n: Int, before: Bool, after: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + self.y = LifetimeTracked(0) + super.init(n: n) + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } + + @objc init?(n: Int, before: Bool, during: Bool, after: Bool) { + do { + let _ = try maybeThrow(before) + } catch { + return nil + } + + self.y = LifetimeTracked(0) + do { + try super.init(n: maybeThrow(during)) + } catch { + return nil + } + + do { + let _ = try maybeThrow(after) + } catch { + return nil + } + } +} + +FailableComposedWithThrowingInitTestSuite.test("FailableInitFailure_Root") { + mustFail { AtObjCBear(n: 0, before: true) } + mustFail { AtObjCBear(n: 0, after: true) } + mustFail { AtObjCBear(n: 0, before: true, after: false) } + mustFail { AtObjCBear(n: 0, before: false, after: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("FailableInitFailure_Derived") { + mustFail { AtObjCPolarBear(n: 0, before: true) } + mustFail { AtObjCPolarBear(n: 0, during: true) } + mustFail { AtObjCPolarBear(n: 0, before: true, during: false) } + mustFail { AtObjCPolarBear(n: 0, before: false, during: true) } + mustFail { AtObjCPolarBear(n: 0, after: true) } + mustFail { AtObjCPolarBear(n: 0, during: true, after: false) } + mustFail { AtObjCPolarBear(n: 0, during: false, after: true) } + mustFail { AtObjCPolarBear(n: 0, before: true, after: false) } + mustFail { AtObjCPolarBear(n: 0, before: false, after: true) } + mustFail { AtObjCPolarBear(n: 0, before: true, during: false, after: false) } + mustFail { AtObjCPolarBear(n: 0, before: false, during: true, after: false) } + mustFail { AtObjCPolarBear(n: 0, before: false, during: false, after: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("ConvenienceInitFailure_Root") { + mustFail { AtObjCBear(before: true) } + mustFail { AtObjCBear(during: true) } + mustFail { AtObjCBear(before: true, during: false) } + mustFail { AtObjCBear(before: false, during: true) } + mustFail { AtObjCBear(after: true) } + mustFail { AtObjCBear(before: true, after: false) } + mustFail { AtObjCBear(before: false, after: true) } + mustFail { AtObjCBear(during: true, after: false) } + mustFail { AtObjCBear(during: false, after: true) } + mustFail { AtObjCBear(before: true, during: false, after: false) } + mustFail { AtObjCBear(before: false, during: true, after: false) } + mustFail { AtObjCBear(before: false, during: false, after: true) } + + _ = AtObjCBear(IUO: false) + _ = AtObjCBear(force: false) + + expectEqual(NSLifetimeTracked.count(), 0) +} + +FailableComposedWithThrowingInitTestSuite.test("ConvenienceInitFailure_Derived") { + mustFail { AtObjCPolarBear(before: true) } + mustFail { AtObjCPolarBear(during: true) } + mustFail { AtObjCPolarBear(before: true, during: false) } + mustFail { AtObjCPolarBear(before: false, during: true) } + mustFail { AtObjCPolarBear(after: true) } + mustFail { AtObjCPolarBear(before: true, after: false) } + mustFail { AtObjCPolarBear(before: false, after: true) } + mustFail { AtObjCPolarBear(during: true, after: false) } + mustFail { AtObjCPolarBear(during: false, after: true) } + mustFail { AtObjCPolarBear(before: true, during: false, after: false) } + mustFail { AtObjCPolarBear(before: false, during: true, after: false) } + mustFail { AtObjCPolarBear(before: false, during: false, after: true) } + expectEqual(NSLifetimeTracked.count(), 0) +} diff --git a/test/Interpreter/objc_throwing_initializers.swift b/test/Interpreter/objc_throwing_initializers.swift index ea6aaca8c2bbb..f8cf7dfd0e24f 100644 --- a/test/Interpreter/objc_throwing_initializers.swift +++ b/test/Interpreter/objc_throwing_initializers.swift @@ -5,6 +5,16 @@ // RUN: %target-codesign %t/a.out // RUN: %target-run %t/a.out +// RUN: %empty-directory(%t) +// +// target-build-swift assumes we want -swift-version 4. Behavior in initializers +// changed in swift 5, so we want to explicitly check it as well. +// +// RUN: %target-clang -fobjc-arc %S/Inputs/ObjCClasses/ObjCClasses.m -c -o %t/ObjCClasses.o +// RUN: %target-build-swift -I %S/Inputs/ObjCClasses/ -Xlinker %t/ObjCClasses.o %s -o %t/a.out -swift-version 5 +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out + // REQUIRES: executable_test // REQUIRES: objc_interop diff --git a/test/SILOptimizer/definite_init_markuninitialized_delegatingself.sil b/test/SILOptimizer/definite_init_markuninitialized_delegatingself.sil index 2f71618c1e085..b41c5bab7eb6d 100644 --- a/test/SILOptimizer/definite_init_markuninitialized_delegatingself.sil +++ b/test/SILOptimizer/definite_init_markuninitialized_delegatingself.sil @@ -31,6 +31,8 @@ class DerivedClassWithNontrivialStoredProperties : RootClassWithNontrivialStored override init() } +sil @getThrowingValue : $@convention(thin) () -> (Int, @error Error) + // CHECK-LABEL: @self_init_assert_instruction // CHECK: apply // CHECK-NEXT: store @@ -323,5 +325,50 @@ bb4: br bb5(%24 : $Optional) bb5(%26 : $Optional): - return %26 : $Optional + return %26 : $Optional +} + +sil @selfinit_derivedclass_peerconvenience_init : $@convention (thin) (@owned DerivedClassWithNontrivialStoredProperties) -> @owned DerivedClassWithNontrivialStoredProperties + +// Make sure that we do not error when we store back in the fail block here. +sil hidden [ossa] @convenience_init_with_throwing_argument : $@convention(method) (@owned DerivedClassWithNontrivialStoredProperties) -> @owned Optional { +bb0(%0 : @owned $DerivedClassWithNontrivialStoredProperties): + %1 = alloc_stack $DerivedClassWithNontrivialStoredProperties, let, name "self" + %2 = mark_uninitialized [delegatingselfallocated] %1 : $*DerivedClassWithNontrivialStoredProperties + store %0 to [init] %2 : $*DerivedClassWithNontrivialStoredProperties + %6 = load [take] %2 : $*DerivedClassWithNontrivialStoredProperties + %7 = function_ref @getThrowingValue : $@convention(thin) () -> (Int, @error Error) + try_apply %7() : $@convention(thin) () -> (Int, @error Error), normal bb1, error bb5 + +bb1(%9 : $Int): + %10 = function_ref @selfinit_derivedclass_peerconvenience_init : $@convention (thin) (@owned DerivedClassWithNontrivialStoredProperties) -> @owned DerivedClassWithNontrivialStoredProperties + %11 = apply %10(%6) : $@convention (thin) (@owned DerivedClassWithNontrivialStoredProperties) -> @owned DerivedClassWithNontrivialStoredProperties + store %11 to [init] %2 : $*DerivedClassWithNontrivialStoredProperties + %13 = load [copy] %2 : $*DerivedClassWithNontrivialStoredProperties + %14 = enum $Optional, #Optional.some!enumelt, %13 : $DerivedClassWithNontrivialStoredProperties + destroy_addr %2 : $*DerivedClassWithNontrivialStoredProperties + dealloc_stack %1 : $*DerivedClassWithNontrivialStoredProperties + br bb3(%14 : $Optional) + +bb2: + destroy_addr %2 : $*DerivedClassWithNontrivialStoredProperties + dealloc_stack %1 : $*DerivedClassWithNontrivialStoredProperties + %20 = enum $Optional, #Optional.none!enumelt + br bb3(%20 : $Optional) + +bb3(%22 : @owned $Optional): + return %22 : $Optional + +bb4(%24 : @owned $Error): + %25 = begin_borrow %24 : $Error + %26 = copy_value %25 : $Error + debug_value %26 : $Error, let, name "error" + destroy_value %26 : $Error + end_borrow %25 : $Error + destroy_value %24 : $Error + br bb2 + +bb5(%32 : @owned $Error): + store %6 to [init] %2 : $*DerivedClassWithNontrivialStoredProperties + br bb4(%32 : $Error) }