Skip to content

Commit d25ce3d

Browse files
authored
Merge pull request swiftlang#35302 from atrick/fix-foreachloop
Fix ForEachLoopUnroll use-after-free miscompile.
2 parents 6147a6b + cec5513 commit d25ce3d

File tree

2 files changed

+25
-13
lines changed

2 files changed

+25
-13
lines changed

lib/SILOptimizer/LoopTransforms/ForEachLoopUnroll.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,24 @@
8888
// alloc_stack %stack
8989
// %elem1borrow = begin_borrow %elem1copy
9090
// store_borrow %elem1borrow to %stack
91-
// end_borrow %elem1borrow
9291
// try_apply %body(%stack) normal normalbb1, error errbb1
9392
//
9493
// errbb1(%error : @owned $Error):
95-
// br bb2(%error)
94+
// end_borrow %elem1borrow
95+
// br bb2(%error)
9696
//
9797
// normalbb1(%res : $()):
98+
// end_borrow %elem1borrow
9899
// %elem2borrow = begin_borrow %elem2copy
99100
// store_borrow %elem2borrow to %stack
100-
// end_borrow %elem2borrow
101101
// try_apply %body(%stack) normal bb1, error errbb2
102102
//
103103
// errbb2(%error : @owned $Error):
104-
// br bb2(%error)
104+
// end_borrow %elem2borrow
105+
// br bb2(%error)
105106
//
106107
// bb1(%res : $()):
108+
// end_borrow %elem2borrow
107109
// dealloc_stack %stack
108110
// ...
109111
// bb2(%error : @owned $Error):
@@ -499,12 +501,16 @@ static void unrollForEach(ArrayInfo &arrayInfo, TryApplyInst *forEachCall,
499501
// A generator for creating a basic block for use as the target of the
500502
// "error" branch of a try_apply. The error block created here will always
501503
// jump to the error target of the original forEach.
502-
auto errorTargetGenerator = [&](SILBasicBlock *insertionBlock) {
504+
auto errorTargetGenerator = [&](SILBasicBlock *insertionBlock,
505+
SILValue borrowedElem ) {
503506
SILBasicBlock *newErrorBB = fun->createBasicBlockBefore(insertionBlock);
504507
SILValue argument = newErrorBB->createPhiArgument(
505508
errorArgument->getType(), errorArgument->getOwnershipKind());
506509
// Make the errorBB jump to the error target of the original forEach.
507510
SILBuilderWithScope builder(newErrorBB, forEachCall);
511+
if (borrowedElem) {
512+
builder.createEndBorrow(forEachLoc, borrowedElem);
513+
}
508514
builder.createBranch(forEachLoc, errorBB, argument);
509515
return newErrorBB;
510516
};
@@ -525,28 +531,34 @@ static void unrollForEach(ArrayInfo &arrayInfo, TryApplyInst *forEachCall,
525531
// error blocks jump to the error target of the original forEach call.
526532
for (uint64_t num = arrayInfo.getNumElements(); num > 0; --num) {
527533
SILValue elementCopy = elementCopies[num - 1];
534+
// Creating the next normal block ends the borrow scope for borrowedElem
535+
// from the previous iteration.
528536
SILBasicBlock *currentBB = num > 1 ? normalTargetGenerator(nextNormalBB)
529537
: forEachCall->getParentBlock();
530538
SILBuilderWithScope unrollBuilder(currentBB, forEachCall);
539+
SILValue borrowedElem;
531540
if (arrayElementType.isTrivial(*fun)) {
532541
unrollBuilder.createStore(forEachLoc, elementCopy, allocStack,
533542
StoreOwnershipQualifier::Trivial);
534543
} else {
535544
// Borrow the elementCopy and store it in the allocStack. Note that the
536545
// element's copy is guaranteed to be alive until the array is alive.
537546
// Therefore it is okay to use a borrow here.
538-
SILValue borrowedElem =
539-
unrollBuilder.createBeginBorrow(forEachLoc, elementCopy);
547+
borrowedElem = unrollBuilder.createBeginBorrow(forEachLoc, elementCopy);
540548
unrollBuilder.createStoreBorrow(forEachLoc, borrowedElem, allocStack);
541-
unrollBuilder.createEndBorrow(forEachLoc, borrowedElem);
549+
550+
SILBuilderWithScope(&nextNormalBB->front(), forEachCall)
551+
.createEndBorrow(forEachLoc, borrowedElem);
542552
}
543-
SILBasicBlock *errorTarget = errorTargetGenerator(nextNormalBB);
553+
554+
SILBasicBlock *errorTarget =
555+
errorTargetGenerator(nextNormalBB, borrowedElem);
544556
// Note that the substitution map must be empty as we are not supporting
545557
// elements of address-only type. All elements in the array are guaranteed
546558
// to be loadable. TODO: generalize this to address-only types.
547559
unrollBuilder.createTryApply(forEachLoc, forEachBodyClosure,
548-
SubstitutionMap(), allocStack, nextNormalBB,
549-
errorTarget);
560+
SubstitutionMap(), allocStack,
561+
nextNormalBB, errorTarget);
550562
nextNormalBB = currentBB;
551563
}
552564

test/SILOptimizer/for_each_loop_unroll_test.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,23 @@ bb2(%39 : @owned $Error):
117117
// CHECK: [[STACK:%[0-9]+]] = alloc_stack $@callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for <Int>
118118
// CHECK: [[ELEM1BORROW:%[0-9]+]] = begin_borrow [[ELEM1]]
119119
// CHECK: store_borrow [[ELEM1BORROW]] to [[STACK]]
120-
// CHECK: end_borrow [[ELEM1BORROW]]
121120
// CHECK: try_apply [[BODYCLOSURE]]([[STACK]]) : $@noescape @callee_guaranteed (@in_guaranteed @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for <Int>) -> @error Error, normal [[NORMAL:bb[0-9]+]], error [[ERROR:bb[0-9]+]]
122121

123122
// CHECK: [[ERROR]]([[ERRPARAM:%[0-9]+]] : @owned $Error):
124123
// CHECK: br [[ERROR3:bb[0-9]+]]([[ERRPARAM]] : $Error)
125124

126125
// CHECK: [[NORMAL]](%{{.*}} : $()):
126+
// CHECK: end_borrow [[ELEM1BORROW]]
127127
// CHECK: [[ELEM2BORROW:%[0-9]+]] = begin_borrow [[ELEM2]]
128128
// CHECK: store_borrow [[ELEM2BORROW]] to [[STACK]]
129-
// CHECK: end_borrow [[ELEM2BORROW]]
130129
// CHECK: try_apply [[BODYCLOSURE]]([[STACK]]) : $@noescape @callee_guaranteed (@in_guaranteed @callee_guaranteed @substituted <τ_0_0> () -> @out τ_0_0 for <Int>) -> @error Error, normal [[NORMAL2:bb[0-9]+]], error [[ERROR2:bb[0-9]+]]
131130

132131
// CHECK: [[ERROR2]]([[ERRPARAM2:%[0-9]+]] : @owned $Error):
133132
// CHECK: br [[ERROR3:bb[0-9]+]]([[ERRPARAM2]] : $Error)
134133

135134
// CHECK: [[NORMAL2]](%{{.*}} : $()):
136135
// CHECK: dealloc_stack [[STACK]]
136+
// CHECK: end_borrow [[ELEM2BORROW]]
137137
// Note that the temporary alloc_stack of the array created for the forEach call
138138
// will be cleaned up when the forEach call is removed.
139139
// CHECK: destroy_value

0 commit comments

Comments
 (0)