Skip to content

Commit b9f184a

Browse files
committed
Fix LifetimeDependenceDiagnostics: scoped dependence on a copy
Diagnose a scoped dependence on an argument that inherits its lifetime as an error: @Lifetime(borrow arg) func reborrowSpan<T>(_ arg: Span<T>) -> Span<T> { arg } @Lifetime(copy span) public func testBorrowInheritedArg<T>(_ span: Span<T>) -> Span<T> { reborrowSpan(span) // expected-error {{lifetime-dependent value escapes its scope}} } Fixes: rdar://146319009 ([nonescapable] enforce borrow constraint narrowing of inherited lifetime)
1 parent 4d21b2e commit b9f184a

File tree

3 files changed

+95
-6
lines changed

3 files changed

+95
-6
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,23 @@ private struct DiagnoseDependence {
202202
// Check that the parameter dependence for this result is the same
203203
// as the current dependence scope.
204204
if let arg = dependence.scope.parentValue as? FunctionArgument,
205-
function.argumentConventions[resultDependsOn: arg.index] != nil {
206-
// The returned value depends on a lifetime that is inherited or
207-
// borrowed in the caller. The lifetime of the argument value
208-
// itself is irrelevant here.
209-
log(" has dependent function result")
210-
return .continueWalk
205+
let argDep = function.argumentConventions[resultDependsOn: arg.index] {
206+
switch argDep {
207+
case .inherit:
208+
if dependence.markDepInst != nil {
209+
// A mark_dependence represents a "borrow" scope. A local borrow scope cannot inherit the caller's dependence
210+
// because the borrow scope depends on the argument value itself, while the caller allows the result to depend
211+
// on a value that the argument was copied from.
212+
break
213+
}
214+
fallthrough
215+
case .scope:
216+
// The returned value depends on a lifetime that is inherited or
217+
// borrowed in the caller. The lifetime of the argument value
218+
// itself is irrelevant here.
219+
log(" has dependent function result")
220+
return .continueWalk
221+
}
211222
}
212223
return .abortWalk
213224
}

test/SILOptimizer/lifetime_dependence/semantics.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,22 @@ func copySpan<T>(_ arg: Span<T>) -> /* */ Span<T> { arg }
241241
@lifetime(borrow arg)
242242
func reborrowSpan<T>(_ arg: Span<T>) -> Span<T> { arg }
243243

244+
@lifetime(&arg)
245+
func reborrowGenericInout<T: ~Escapable>(_ arg: inout T) -> T { arg }
246+
247+
@lifetime(copy arg)
248+
func inheritSpan<T>(_ arg: Span<T>) -> Span<T> { arg }
249+
250+
@lifetime(copy arg)
251+
func inheritGeneric<T: ~Escapable>(_ arg: consuming T) -> T { arg }
252+
253+
public struct NE: ~Escapable {}
254+
255+
@lifetime(&ne)
256+
func borrowNE<T: ~Escapable>(ne: inout T) -> T {
257+
ne
258+
}
259+
244260
// =============================================================================
245261
// Initialization
246262
// =============================================================================
@@ -340,6 +356,30 @@ func testInheritedCopyVar(_ arg: [Int] ) {
340356
parse(result) // ✅ Safe: within lifetime of 'a'
341357
}
342358

359+
@lifetime(copy span)
360+
public func testBorrowInheritedArg<T>(_ span: Span<T>) -> Span<T> {
361+
reborrowSpan(span) // expected-error {{lifetime-dependent value escapes its scope}}
362+
// expected-note @-2{{it depends on the lifetime of argument 'span'}}
363+
} // expected-note {{this use causes the lifetime-dependent value to escape}}
364+
365+
@lifetime(copy t)
366+
public func testBorrowInheritedInoutArg<T: ~Escapable>(_ t: inout T) -> T {
367+
// expected-error @-1{{lifetime-dependent variable 't' escapes its scope}}
368+
// expected-note @-2{{it depends on the lifetime of argument 't'}}
369+
reborrowGenericInout(&t)
370+
// expected-note @-1{{this use causes the lifetime-dependent value to escape}}
371+
}
372+
373+
@lifetime(copy span)
374+
public func testCopyInheritedArg<T>(_ span: Span<T>) -> Span<T> {
375+
inheritSpan(span)
376+
}
377+
378+
@lifetime(copy t)
379+
public func testCopyInheritedGenericArg<T: ~Escapable>(_ t: consuming T) -> T {
380+
inheritGeneric(t)
381+
}
382+
343383
// =============================================================================
344384
// Scoped dependence on inherited dependence
345385
// =============================================================================
@@ -364,6 +404,23 @@ func testScopedOfInheritedWithLet(_ arg: [Int] ) {
364404
_ = result
365405
} // expected-note {{this use of the lifetime-dependent value is out of scope}}
366406

407+
// Test a scoped dependence on an inherited inout argument.
408+
//
409+
// If testScopedOfInheritedWithLet is not an error, then its result can outlive its borrowed value:
410+
// let ne1 = NE()
411+
// var ne2 = ne1
412+
// let dep = foo(ne: &ne2)
413+
// _ = consume ne2
414+
// _ = dep
415+
//
416+
@lifetime(copy ne)
417+
public func testScopedOfInheritedInout<T: ~Escapable>(ne: inout T) -> T {
418+
// expected-error @-1{{lifetime-dependent variable 'ne' escapes its scope}}
419+
// expected-note @-2{{it depends on the lifetime of argument 'ne'}}
420+
borrowNE(ne: &ne)
421+
// expected-note @-1{{this use causes the lifetime-dependent value to escape}}
422+
}
423+
367424
// =============================================================================
368425
// Scoped dependence on trivial values
369426
// =============================================================================

test/SILOptimizer/lifetime_dependence/verify_diagnostics.sil

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ sil @useA : $@convention(thin) (A) -> ()
4949
sil @makeNE : $@convention(thin) () -> @lifetime(immortal) @owned NE
5050
sil @makeNEObject : $@convention(thin) () -> @lifetime(immortal) @owned NEObject
5151
sil @useNE : $@convention(thin) (@guaranteed NE) -> ()
52+
sil @reborrowNE : $@convention(thin) (@guaranteed NE) -> @lifetime(borrow 0) @owned NE
5253

5354
sil @initHolder : $@convention(thin) () -> @out Holder
5455
sil @getNE : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address_for_deps 0) @owned NE
@@ -167,6 +168,26 @@ bb0:
167168
return %12
168169
}
169170

171+
// Test that a borrowed dependency cannot inherit a copied dependency.
172+
sil [ossa] @testReborrow : $@convention(thin) (@guaranteed NE) -> @lifetime(copy 0) @owned NE {
173+
bb0(%0 : @guaranteed $NE):
174+
debug_value %0, let, name "ne", argno 1
175+
%2 = function_ref @reborrowNE : $@convention(thin) (@guaranteed NE) -> @lifetime(borrow 0) @owned NE
176+
%3 = apply %2(%0) : $@convention(thin) (@guaranteed NE) -> @lifetime(borrow 0) @owned NE
177+
// expected-error @-1{{lifetime-dependent value escapes its scope}}
178+
%4 = mark_dependence [unresolved] %3 on %0
179+
return %4 // expected-note {{this use causes the lifetime-dependent value to escape}}
180+
}
181+
182+
sil [ossa] @testBorrowValue : $@convention(thin) (@guaranteed NE) -> @lifetime(copy 0) @owned NE {
183+
bb0(%0 : @guaranteed $NE):
184+
%2 = function_ref @reborrowNE : $@convention(thin) (@guaranteed NE) -> @lifetime(borrow 0) @owned NE
185+
%3 = apply %2(%0) : $@convention(thin) (@guaranteed NE) -> @lifetime(borrow 0) @owned NE
186+
// expected-error @-1{{lifetime-dependent value escapes its scope}}
187+
%4 = mark_dependence [unresolved] %3 on %0
188+
return %4 // expected-note {{this use causes the lifetime-dependent value to escape}}
189+
}
190+
170191
// Test a borrowed dependency on an address
171192
sil [ossa] @testBorrowAddress : $@convention(thin) <T where T : ~Escapable> (@thick T.Type) -> @lifetime(immortal) @out T {
172193
bb0(%0 : $*T, %1 : $@thick T.Type):

0 commit comments

Comments
 (0)