Skip to content

Commit 42fef1f

Browse files
committed
SILGen: let withoutActuallyEscaping also catch an escaping closure in the throwing case
withoutActuallyEscaping checks that the passed closure has not beed escaped by ensuring that its reference count is exactly 1 at the end of the code block. So far this was only done in the regular return case. But if the code block throws, the check was not done. Fixes an undetected undefined behavior.
1 parent 33eab01 commit 42fef1f

File tree

3 files changed

+59
-11
lines changed

3 files changed

+59
-11
lines changed

lib/SILGen/SILGenExpr.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6041,6 +6041,34 @@ RValue RValueEmitter::visitOpenExistentialExpr(OpenExistentialExpr *E,
60416041
});
60426042
}
60436043

6044+
namespace {
6045+
class DestroyNotEscapedClosureCleanup : public Cleanup {
6046+
SILValue v;
6047+
public:
6048+
DestroyNotEscapedClosureCleanup(SILValue v) : v(v) {}
6049+
6050+
void emit(SILGenFunction &SGF, CleanupLocation l,
6051+
ForUnwind_t forUnwind) override {
6052+
// Now create the verification of the withoutActuallyEscaping operand.
6053+
// Either we fail the uniqueness check (which means the closure has escaped)
6054+
// and abort or we continue and destroy the ultimate reference.
6055+
auto isEscaping = SGF.B.createIsEscapingClosure(
6056+
l, v, IsEscapingClosureInst::WithoutActuallyEscaping);
6057+
SGF.B.createCondFail(l, isEscaping, "non-escaping closure has escaped");
6058+
SGF.B.createDestroyValue(l, v);
6059+
}
6060+
6061+
void dump(SILGenFunction &) const override {
6062+
#ifndef NDEBUG
6063+
llvm::errs() << "DestroyNotEscapedClosureCleanup\n"
6064+
<< "State:" << getState() << "\n"
6065+
<< "Value:" << v << "\n";
6066+
#endif
6067+
}
6068+
};
6069+
} // end anonymous namespace
6070+
6071+
60446072
RValue RValueEmitter::visitMakeTemporarilyEscapableExpr(
60456073
MakeTemporarilyEscapableExpr *E, SGFContext C) {
60466074
// Emit the non-escaping function value.
@@ -6075,19 +6103,15 @@ RValue RValueEmitter::visitMakeTemporarilyEscapableExpr(
60756103
}
60766104

60776105
// Convert it to an escaping function value.
6078-
auto escapingClosure =
6106+
auto ec =
60796107
SGF.createWithoutActuallyEscapingClosure(E, functionValue, escapingFnTy);
6108+
SILValue ecv = ec.forward(SGF);
6109+
SGF.Cleanups.pushCleanup<DestroyNotEscapedClosureCleanup>(ecv);
6110+
auto escapingClosure = ManagedValue::forOwnedRValue(ecv, SGF.Cleanups.getTopCleanup());
60806111
auto loc = SILLocation(E);
60816112
auto borrowedClosure = escapingClosure.borrow(SGF, loc);
60826113
RValue rvalue = visitSubExpr(borrowedClosure, false /* isClosureConsumable */);
60836114

6084-
// Now create the verification of the withoutActuallyEscaping operand.
6085-
// Either we fail the uniqueness check (which means the closure has escaped)
6086-
// and abort or we continue and destroy the ultimate reference.
6087-
auto isEscaping = SGF.B.createIsEscapingClosure(
6088-
loc, borrowedClosure.getValue(),
6089-
IsEscapingClosureInst::WithoutActuallyEscaping);
6090-
SGF.B.createCondFail(loc, isEscaping, "non-escaping closure has escaped");
60916115
return rvalue;
60926116
}
60936117

test/Interpreter/withoutActuallyEscaping.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var testShouldThrow = false
2121

2222
struct MyError : Error {}
2323

24+
@inline(never)
2425
func letEscapeThrowing(f: () -> ()) throws -> () -> () {
2526
return try withoutActuallyEscaping(f) {
2627
if testShouldThrow {
@@ -30,6 +31,15 @@ func letEscapeThrowing(f: () -> ()) throws -> () -> () {
3031
}
3132
}
3233

34+
@inline(never)
35+
func letEscapeOnThrowingPath(f: () throws -> ()) throws {
36+
try withoutActuallyEscaping(f) {
37+
sink = $0
38+
throw MyError()
39+
}
40+
}
41+
42+
3343
WithoutEscapingSuite.test("ExpectNoCrash") {
3444
dontEscape(f: { print("foo") })
3545
}
@@ -53,7 +63,7 @@ WithoutEscapingSuite.test("ExpectCrash") {
5363
sink = letEscape(f: { print("Context: \(context.a) \(context.b)") })
5464
}
5565

56-
WithoutEscapingSuite.test("ExpectThrowingCrash") {
66+
WithoutEscapingSuite.test("ExpectNonThrowingCrash") {
5767
expectCrashLater()
5868
let context = Context()
5969
var testDidThrow = false
@@ -78,4 +88,16 @@ WithoutEscapingSuite.test("ExpectThrowingNoCrash") {
7888
expectTrue(testDidThrow)
7989
}
8090

91+
WithoutEscapingSuite.test("ExpectThrowingCrash") {
92+
expectCrashLater()
93+
let context = Context()
94+
var testDidThrow = false
95+
do {
96+
try letEscapeOnThrowingPath(f: { print("Context: \(context.a) \(context.b)") })
97+
} catch {
98+
testDidThrow = true
99+
}
100+
expectTrue(testDidThrow)
101+
}
102+
81103
runAllTests()

test/SILGen/without_actually_escaping.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ func letEscape(f: () -> ()) -> () -> () {
3535
// CHECK: try_apply [[USER]]([[BORROW]]) : {{.*}}, normal bb1, error bb2
3636
//
3737
// CHECK: bb1([[RES:%.*]] : @owned $@callee_guaranteed () -> ()):
38-
// CHECK: [[ESCAPED:%.*]] = is_escaping_closure [[BORROW]]
39-
// CHECK: cond_fail [[ESCAPED]] : $Builtin.Int1
4038
// CHECK: end_borrow [[BORROW]]
39+
// CHECK: [[ESCAPED:%.*]] = is_escaping_closure [[MD]]
40+
// CHECK: cond_fail [[ESCAPED]] : $Builtin.Int1
4141
// CHECK: destroy_value [[MD]]
4242
// CHECK: return [[RES]]
4343
//
4444
// CHECK: bb2([[ERR:%.*]] : @owned $any Error):
4545
// CHECK: end_borrow [[BORROW]]
46+
// CHECK: [[ESCAPED:%.*]] = is_escaping_closure [[MD]]
47+
// CHECK: cond_fail [[ESCAPED]] : $Builtin.Int1
4648
// CHECK: destroy_value [[MD]]
4749
// CHECK: throw [[ERR]] : $any Error
4850
// CHECK: }

0 commit comments

Comments
 (0)