Skip to content

Commit 50e9cc3

Browse files
authored
Merge pull request #29882 from eeckstein/fix-sil-combine
SILCombine: fix a miscompile in the alloc_stack optimization which causes a use-after-free.
2 parents fb838b5 + a89340c commit 50e9cc3

File tree

3 files changed

+190
-2
lines changed

3 files changed

+190
-2
lines changed

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,36 @@ struct AllocStackAnalyzer : SILInstructionVisitor<AllocStackAnalyzer> {
417417

418418
} // end anonymous namespace
419419

420+
/// Returns true if there is a retain instruction between \p from and the
421+
/// destroy or deallocation of \p alloc.
422+
static bool somethingIsRetained(SILInstruction *from, AllocStackInst *alloc) {
423+
llvm::SmallVector<SILInstruction *, 8> workList;
424+
llvm::SmallPtrSet<SILBasicBlock *, 8> handled;
425+
workList.push_back(from);
426+
while (!workList.empty()) {
427+
SILInstruction *start = workList.pop_back_val();
428+
for (auto iter = start->getIterator(), end = start->getParent()->end();
429+
iter != end;
430+
++iter) {
431+
SILInstruction *inst = &*iter;
432+
if (isa<RetainValueInst>(inst) || isa<StrongRetainInst>(inst)) {
433+
return true;
434+
}
435+
if ((isa<DeallocStackInst>(inst) || isa<DestroyAddrInst>(inst)) &&
436+
inst->getOperand(0) == alloc) {
437+
break;
438+
}
439+
if (isa<TermInst>(inst)) {
440+
for (SILBasicBlock *succ : start->getParent()->getSuccessors()) {
441+
if (handled.insert(succ).second)
442+
workList.push_back(&*succ->begin());
443+
}
444+
}
445+
}
446+
}
447+
return false;
448+
}
449+
420450
SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) {
421451
// If we are testing SILCombine and we are asked not to eliminate
422452
// alloc_stacks, just return.
@@ -477,15 +507,15 @@ SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) {
477507
//
478508
// TODO: Do we not remove purely dead live ranges here? Seems like we should.
479509
SmallPtrSet<SILInstruction *, 16> ToDelete;
510+
SmallVector<CopyAddrInst *, 4> takingCopies;
480511

481512
for (auto *Op : AS->getUses()) {
482513
// Replace a copy_addr [take] %src ... by a destroy_addr %src if %src is
483514
// no the alloc_stack.
484515
// Otherwise, just delete the copy_addr.
485516
if (auto *CopyAddr = dyn_cast<CopyAddrInst>(Op->getUser())) {
486517
if (CopyAddr->isTakeOfSrc() && CopyAddr->getSrc() != AS) {
487-
Builder.setInsertionPoint(CopyAddr);
488-
Builder.createDestroyAddr(CopyAddr->getLoc(), CopyAddr->getSrc());
518+
takingCopies.push_back(CopyAddr);
489519
}
490520
}
491521

@@ -506,6 +536,20 @@ SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) {
506536
ToDelete.insert(Op->getUser());
507537
}
508538

539+
// Check if a retain is moved after the copy_addr. If the retained object
540+
// happens to be the source of the copy_addr it might be only kept alive by
541+
// the stack location. This cannot happen with OSSA.
542+
// TODO: remove this check once we have OSSA.
543+
for (CopyAddrInst *copy : takingCopies) {
544+
if (somethingIsRetained(copy, AS))
545+
return nullptr;
546+
}
547+
548+
for (CopyAddrInst *copy : takingCopies) {
549+
SILBuilderWithScope destroyBuilder(copy, Builder.getBuilderContext());
550+
destroyBuilder.createDestroyAddr(copy->getLoc(), copy->getSrc());
551+
}
552+
509553
// Erase the 'live-range'
510554
for (auto *Inst : ToDelete) {
511555
Inst->replaceAllUsesOfAllResultsWithUndef();

test/SILOptimizer/sil_combine.sil

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2640,6 +2640,107 @@ bb0(%0 : $*B):
26402640
return %4 : $()
26412641
}
26422642

2643+
// CHECK-LABEL: sil @moved_retain_prevents_alloc_stack_deletion1
2644+
// CHECK: copy_addr
2645+
// CHECK: strong_retain
2646+
// CHECK: destroy_addr
2647+
// CHECK: } // end sil function 'moved_retain_prevents_alloc_stack_deletion1'
2648+
sil @moved_retain_prevents_alloc_stack_deletion1 : $@convention(thin) (@guaranteed B) -> () {
2649+
bb0(%0 : $B):
2650+
%3 = alloc_stack $B
2651+
%4 = alloc_stack $B
2652+
store %0 to %4 : $*B
2653+
copy_addr [take] %4 to [initialization] %3 : $*B
2654+
dealloc_stack %4 : $*B
2655+
strong_retain %0 : $B
2656+
destroy_addr %3 : $*B
2657+
dealloc_stack %3 : $*B
2658+
%14 = tuple ()
2659+
return %14 : $()
2660+
}
2661+
2662+
// CHECK-LABEL: sil @moved_retain_prevents_alloc_stack_deletion2
2663+
// CHECK: copy_addr
2664+
// CHECK: bb1:
2665+
// CHECK: strong_retain
2666+
// CHECK: destroy_addr
2667+
// CHECK: bb2:
2668+
// CHECK: strong_retain
2669+
// CHECK: destroy_addr
2670+
// CHECK: } // end sil function 'moved_retain_prevents_alloc_stack_deletion2'
2671+
sil @moved_retain_prevents_alloc_stack_deletion2 : $@convention(thin) (@guaranteed B) -> () {
2672+
bb0(%0 : $B):
2673+
%3 = alloc_stack $B
2674+
%4 = alloc_stack $B
2675+
store %0 to %4 : $*B
2676+
copy_addr [take] %4 to [initialization] %3 : $*B
2677+
dealloc_stack %4 : $*B
2678+
cond_br undef, bb1, bb2
2679+
bb1:
2680+
strong_retain %0 : $B
2681+
destroy_addr %3 : $*B
2682+
dealloc_stack %3 : $*B
2683+
br bb3
2684+
bb2:
2685+
strong_retain %0 : $B
2686+
destroy_addr %3 : $*B
2687+
dealloc_stack %3 : $*B
2688+
br bb3
2689+
bb3:
2690+
%14 = tuple ()
2691+
return %14 : $()
2692+
}
2693+
2694+
// CHECK-LABEL: sil @retain_is_after_stack_location
2695+
// CHECK-NOT: copy_addr
2696+
// CHECK: } // end sil function 'retain_is_after_stack_location'
2697+
sil @retain_is_after_stack_location : $@convention(thin) (@guaranteed B) -> () {
2698+
bb0(%0 : $B):
2699+
%3 = alloc_stack $B
2700+
%4 = alloc_stack $B
2701+
store %0 to %4 : $*B
2702+
copy_addr [take] %4 to [initialization] %3 : $*B
2703+
dealloc_stack %4 : $*B
2704+
cond_br undef, bb1, bb2
2705+
bb1:
2706+
destroy_addr %3 : $*B
2707+
strong_retain %0 : $B
2708+
dealloc_stack %3 : $*B
2709+
br bb3
2710+
bb2:
2711+
destroy_addr %3 : $*B
2712+
strong_retain %0 : $B
2713+
dealloc_stack %3 : $*B
2714+
br bb3
2715+
bb3:
2716+
%14 = tuple ()
2717+
return %14 : $()
2718+
}
2719+
2720+
// CHECK-LABEL: sil @moved_retain_prevents_alloc_stack_deletion3
2721+
// CHECK: copy_addr
2722+
// CHECK: bb2:
2723+
// CHECK: strong_retain
2724+
// CHECK: destroy_addr
2725+
// CHECK: } // end sil function 'moved_retain_prevents_alloc_stack_deletion3'
2726+
sil @moved_retain_prevents_alloc_stack_deletion3 : $@convention(thin) (@guaranteed B) -> () {
2727+
bb0(%0 : $B):
2728+
%3 = alloc_stack $B
2729+
%4 = alloc_stack $B
2730+
store %0 to %4 : $*B
2731+
copy_addr [take] %4 to [initialization] %3 : $*B
2732+
dealloc_stack %4 : $*B
2733+
br bb1
2734+
bb1:
2735+
cond_br undef, bb1, bb2
2736+
bb2:
2737+
strong_retain %0 : $B
2738+
destroy_addr %3 : $*B
2739+
dealloc_stack %3 : $*B
2740+
%14 = tuple ()
2741+
return %14 : $()
2742+
}
2743+
26432744
protocol someProtocol {
26442745
var i: Int { get }
26452746
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -O %s -o %t/a.out
3+
// RUN: %target-run %t/a.out | %FileCheck %s
4+
5+
protocol E {
6+
func f() -> Bool
7+
}
8+
9+
final class K {
10+
deinit {
11+
print("deinit")
12+
}
13+
}
14+
15+
16+
struct X : E {
17+
var x: K
18+
func f() -> Bool { return true }
19+
}
20+
21+
func g<T>(_ x : T) -> Bool {
22+
if let y = x as? E { return y.f() }
23+
return false
24+
}
25+
26+
// CHECK that there is no use-after-free in this function.
27+
@inline(never)
28+
func foo(_ x: X) -> Bool {
29+
return g(x)
30+
}
31+
32+
@inline(never)
33+
func testit() {
34+
let x = X(x: K())
35+
_ = foo(x)
36+
print(x)
37+
}
38+
39+
// CHECK: X(x: a.K)
40+
// CHECK: deinit
41+
testit()
42+
43+

0 commit comments

Comments
 (0)