Add memory lifetime verification support for borrow accessors

This commit is contained in:
Meghana Gupta
2025-10-30 12:47:10 -07:00
parent d50aa8ad87
commit 21cc1a185b
7 changed files with 157 additions and 38 deletions

View File

@@ -338,15 +338,18 @@ public:
}
bool hasAddressResult() const {
return hasGuaranteedAddressResult() || hasInoutResult();
}
bool hasGuaranteedAddressResult() const {
if (funcTy->getNumResults() != 1) {
return false;
}
auto resultConvention = funcTy->getResults()[0].getConvention();
if (silConv.loweredAddresses) {
return resultConvention == ResultConvention::GuaranteedAddress ||
resultConvention == ResultConvention::Inout;
if (!silConv.loweredAddresses) {
return false;
}
return resultConvention == ResultConvention::Inout;
auto resultConvention = funcTy->getResults()[0].getConvention();
return resultConvention == ResultConvention::GuaranteedAddress;
}
struct SILResultTypeFunc;

View File

@@ -3155,6 +3155,10 @@ public:
return getSubstCalleeConv().hasGuaranteedResult();
}
bool hasGuaranteedAddressResult() const {
return getSubstCalleeConv().hasGuaranteedAddressResult();
}
bool hasAddressResult() const {
return getSubstCalleeConv().hasAddressResult();
}

View File

@@ -66,10 +66,15 @@ MemoryLocations::Location::Location(SILValue val, unsigned index, int parentIdx)
representativeValue(val),
parentIdx(parentIdx) {
assert(((parentIdx >= 0) ==
(isa<StructElementAddrInst>(val) || isa<TupleElementAddrInst>(val) ||
isa<InitEnumDataAddrInst>(val) || isa<UncheckedTakeEnumDataAddrInst>(val) ||
isa<InitExistentialAddrInst>(val) || isa<OpenExistentialAddrInst>(val)))
&& "sub-locations can only be introduced with struct/tuple/enum projections");
(isa<StructElementAddrInst>(val) || isa<TupleElementAddrInst>(val) ||
isa<InitEnumDataAddrInst>(val) ||
isa<UncheckedTakeEnumDataAddrInst>(val) ||
isa<InitExistentialAddrInst>(val) ||
isa<OpenExistentialAddrInst>(val) || isa<ApplyInst>(val) ||
isa<StoreBorrowInst>(val))) &&
"sub-locations can only be introduced with "
"struct/tuple/enum/store_borrow/borrow accessor "
"projections");
setBitAndResize(subLocations, index);
setBitAndResize(selfAndParents, index);
}
@@ -349,6 +354,21 @@ bool MemoryLocations::analyzeLocationUsesRecursively(SILValue V, unsigned locIdx
if (cast<DebugValueInst>(user)->hasAddrVal())
break;
return false;
case SILInstructionKind::ApplyInst: {
auto *apply = cast<ApplyInst>(user);
if (apply->hasAddressResult()) {
if (!analyzeAddrProjection(apply, locIdx, 0, collectedVals,
subLocationMap))
return false;
}
break;
}
case SILInstructionKind::StoreBorrowInst: {
if (!analyzeAddrProjection(cast<StoreBorrowInst>(user), locIdx, 0,
collectedVals, subLocationMap))
return false;
break;
}
case SILInstructionKind::InjectEnumAddrInst:
case SILInstructionKind::SelectEnumAddrInst:
case SILInstructionKind::ExistentialMetatypeInst:
@@ -357,13 +377,11 @@ bool MemoryLocations::analyzeLocationUsesRecursively(SILValue V, unsigned locIdx
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::LoadInst:
case SILInstructionKind::StoreInst:
case SILInstructionKind::StoreBorrowInst:
case SILInstructionKind::EndAccessInst:
case SILInstructionKind::DestroyAddrInst:
case SILInstructionKind::CheckedCastAddrBranchInst:
case SILInstructionKind::UncheckedRefCastAddrInst:
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
case SILInstructionKind::ApplyInst:
case SILInstructionKind::TryApplyInst:
case SILInstructionKind::BeginApplyInst:
case SILInstructionKind::CopyAddrInst:
@@ -371,6 +389,7 @@ bool MemoryLocations::analyzeLocationUsesRecursively(SILValue V, unsigned locIdx
case SILInstructionKind::DeallocStackInst:
case SILInstructionKind::SwitchEnumAddrInst:
case SILInstructionKind::WitnessMethodInst:
case SILInstructionKind::EndBorrowInst:
break;
case SILInstructionKind::MarkUnresolvedMoveAddrInst:
// We do not want the memory lifetime verifier to verify move_addr inst

View File

@@ -335,7 +335,7 @@ bool MemoryLifetimeVerifier::applyMayRead(Operand *argOp, SILValue addr) {
void MemoryLifetimeVerifier::requireNoStoreBorrowLocation(
SILValue addr, SILInstruction *where) {
if (isa<StoreBorrowInst>(addr)) {
if (isStoreBorrowLocation(addr)) {
reportError("store-borrow location cannot be written",
locations.getLocationIdx(addr), where);
}

View File

@@ -8,26 +8,14 @@ public struct Wrapper {
var k: Klass
}
public struct GenWrapper<T> {
@_hasStorage var _prop: T { get set }
public var prop: T
}
sil [ossa] @borrow_loadable_prop : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass {
bb0(%0 : @guaranteed $Wrapper):
%2 = struct_extract %0, #Wrapper._k
return %2
}
sil [ossa] @borrow_addressonly_prop : $@convention(method) <T> (@in_guaranteed GenWrapper<T>) -> @guaranteed_address T {
bb0(%0 : $*GenWrapper<T>):
%2 = struct_element_addr %0, #GenWrapper._prop
return %2
}
sil @get_wrapper : $@convention(thin) () -> @owned Klass
sil @use_klass : $@convention(thin) (@guaranteed Klass) -> ()
sil @use_T : $@convention(thin) <T> (@in_guaranteed T) -> ()
// CHECK-LABEL: Error#: 0. Begin Error in Function: 'test_end_borrow_on_guaranteed_return_value'
// CHECK: Invalid End Borrow!
@@ -151,15 +139,3 @@ bb0(%0 : @owned $Wrapper):
return %6
}
// TODO: Add verification support in MemoryLifetimeVerifier
sil [ossa] @test_use_after_free_address_only : $@convention(thin) <T> (@in GenWrapper<T>) -> () {
bb0(%0 : $*GenWrapper<T>):
%1 = function_ref @borrow_addressonly_prop : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
%2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
%3 = function_ref @use_T : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
destroy_addr %0
%5 = apply %3<T>(%2) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
%6 = tuple ()
return %6
}

View File

@@ -532,6 +532,17 @@ bb0(%0 : @guaranteed $T):
return %res : $()
}
sil [ossa] @test_store_borrow_nested : $@convention(thin) (@guaranteed Inner) -> () {
bb0(%0 : @guaranteed $Inner):
%s = alloc_stack $Inner
%sb = store_borrow %0 to %s
%elem = struct_element_addr %sb, #Inner.a
end_borrow %sb
dealloc_stack %s
%res = tuple ()
return %res : $()
}
sil [ossa] @test_cast_br_take_always : $@convention(thin) <U, V> (@in U) -> () {
bb0(%0 : $*U):
%s = alloc_stack $V

View File

@@ -29,9 +29,24 @@ struct Mixed {
var i: Int
}
public struct InnerWrapper<T> {
@_hasStorage var _prop: T { get set }
public var prop: T
}
public struct GenWrapper<T> {
@_hasStorage var _prop: T { get set }
public var prop: T
@_hasStorage var _nestedProp: InnerWrapper<T> { get set }
public var nestedProp: InnerWrapper<T>
}
sil @use_owned : $@convention(thin) (@owned T) -> ()
sil @use_guaranteed : $@convention(thin) (@guaranteed T) -> ()
sil @get_owned : $@convention(thin) () -> @owned T
sil @use_T : $@convention(thin) <T> (@in_guaranteed T) -> ()
sil @mutate_T : $@convention(thin) <T> (@inout T) -> ()
// CHECK: SIL memory lifetime failure in @test_simple: indirect argument is not alive at function return
sil [ossa] @test_simple : $@convention(thin) (@inout T) -> @owned T {
@@ -276,8 +291,8 @@ bb0(%0 : $*T):
return %res : $()
}
// CHECK: SIL memory lifetime failure in @test_store_borrow_destroy: store-borrow location cannot be written
sil [ossa] @test_store_borrow_destroy : $@convention(thin) (@guaranteed T) -> () {
// CHECK: SIL memory lifetime failure in @test_store_borrow_destroy1: store-borrow location cannot be written
sil [ossa] @test_store_borrow_destroy1 : $@convention(thin) (@guaranteed T) -> () {
bb0(%0 : @guaranteed $T):
%s = alloc_stack $T
%sb = store_borrow %0 to %s : $*T
@@ -288,6 +303,19 @@ bb0(%0 : @guaranteed $T):
return %res : $()
}
// CHECK: SIL memory lifetime failure in @test_store_borrow_destroy2: store-borrow location cannot be written
sil [ossa] @test_store_borrow_destroy2 : $@convention(thin) (@guaranteed Inner) -> () {
bb0(%0 : @guaranteed $Inner):
%s = alloc_stack $Inner
%sb = store_borrow %0 to %s
%elem = struct_element_addr %sb, #Inner.a
destroy_addr %elem
end_borrow %sb
dealloc_stack %s
%res = tuple ()
return %res : $()
}
sil [ossa] @func_with_inout_param : $@convention(thin) (@inout T) -> ()
// T-CHECK: SIL memory lifetime failure in @test_store_borrow_inout: store-borrow location cannot be written
@@ -842,3 +870,81 @@ bb0(%0 : @owned $T, %1 : @owned $Inner):
dealloc_stack %2
return %8
}
sil [ossa] @borrow_addressonly_prop : $@convention(method) <T> (@in_guaranteed GenWrapper<T>) -> @guaranteed_address T {
bb0(%0 : $*GenWrapper<T>):
%2 = struct_element_addr %0, #GenWrapper._prop
return %2
}
sil [ossa] @mutate_addressonly_prop : $@convention(method) <T> (@inout GenWrapper<T>) -> @inout T {
bb0(%0 : $*GenWrapper<T>):
%2 = struct_element_addr %0, #GenWrapper._prop
return %2
}
sil [ossa] @borrow_addressonly_nested_prop : $@convention(method) <T> (@in_guaranteed GenWrapper<T>) -> @guaranteed_address InnerWrapper<T> {
bb0(%0 : $*GenWrapper<T>):
%2 = struct_element_addr %0, #GenWrapper._nestedProp
return %2
}
sil [ossa] @mutate_addressonly_nested_prop : $@convention(method) <T> (@inout GenWrapper<T>) -> @inout InnerWrapper<T> {
bb0(%0 : $*GenWrapper<T>):
%2 = struct_element_addr %0, #GenWrapper._nestedProp
return %2
}
// CHECK: SIL memory lifetime failure in @test_use_after_free_address_only1: memory is not initialized, but should be
// CHECK: memory location: %2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
// CHECK: at instruction: %5 = apply %3<T>(%2) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
sil [ossa] @test_use_after_free_address_only1 : $@convention(thin) <T> (@in GenWrapper<T>) -> () {
bb0(%0 : $*GenWrapper<T>):
%1 = function_ref @borrow_addressonly_prop : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
%2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
%3 = function_ref @use_T : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
destroy_addr %0
%5 = apply %3<T>(%2) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
%6 = tuple ()
return %6
}
// CHECK: SIL memory lifetime failure in @test_use_after_free_address_only2: memory is not initialized, but should be
// CHECK: memory location: %2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@inout GenWrapper<τ_0_0>) -> @inout τ_0_0
// CHECK: at instruction: %5 = apply %3<T>(%2) : $@convention(thin) <τ_0_0> (@inout τ_0_0) -> ()
sil [ossa] @test_use_after_free_address_only2 : $@convention(thin) <T> (@in GenWrapper<T>) -> () {
bb0(%0 : $*GenWrapper<T>):
%1 = function_ref @mutate_addressonly_prop : $@convention(method) <τ_0_0> (@inout GenWrapper<τ_0_0>) -> @inout τ_0_0
%2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@inout GenWrapper<τ_0_0>) -> @inout τ_0_0
%3 = function_ref @mutate_T : $@convention(thin) <τ_0_0> (@inout τ_0_0) -> ()
destroy_addr %0
%5 = apply %3<T>(%2) : $@convention(thin) <τ_0_0> (@inout τ_0_0) -> ()
%6 = tuple ()
return %6
}
// TODO-CHECK: SIL memory lifetime failure in @test_guaranteed_address_consume: store-borrow location cannot be written
// TODO-CHECK: memory location: %2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
// TODO-CHECK: at instruction: destroy_addr %2 : $*T
sil [ossa] @test_guaranteed_address_consume : $@convention(thin) <T> (@in GenWrapper<T>) -> () {
bb0(%0 : $*GenWrapper<T>):
%1 = function_ref @borrow_addressonly_prop : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
%2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address τ_0_0
destroy_addr %2
%4 = tuple ()
return %4
}
// TODO-CHECK: SIL memory lifetime failure in @test_guaranteed_nested_address_consume: store-borrow location cannot be written
// TODO-CHECK: memory location: %3 = struct_element_addr %2 : $*InnerWrapper<T>, #InnerWrapper._prop
// TODO-CHECK: at instruction: destroy_addr %3 : $*T
sil [ossa] @test_guaranteed_nested_address_consume : $@convention(thin) <T> (@in GenWrapper<T>) -> () {
bb0(%0 : $*GenWrapper<T>):
%1 = function_ref @borrow_addressonly_nested_prop : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address InnerWrapper<τ_0_0>
%2 = apply %1<T>(%0) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_address InnerWrapper<τ_0_0>
%3 = struct_element_addr %2, #InnerWrapper._prop
destroy_addr %3
%4 = tuple ()
return %4
}