Skip to content

Commit 601a51a

Browse files
committed
Fix EscapeAnalysis connection graph for existential values.
Change the connection graph builder to map an existential address and its opened address onto the same node. Fixes <rdar://59559805> miscompile; use-after-free Immediate bug: EscapeAnalysis::mayReleaseContent incorrectly returns 'false' when comparing a store into an existential's value with a release of the existential. How this was exposed: mayReleaseContent was made more aggressive so that only the connection graph node representing the released object is considered to be "released". This fails for existentials because a separate connection graph node is created for the value within the existential. This is just one manifestation of a broader bug. Representing an existential as two logically distinct objects could also result in incorrect escaping information and incorrect alias analysis. Note that copying directly into an existential address and storing into the opened value in fact modify the same memory. Furthermore, when opening an existential, no local reference count is used to keep the opened value "alive" independent from the existential (this is another assumption made by mayReleaseContent). This is always how existentials were represented in the connection graph, but issues had never been exposed.
1 parent 5f0ccca commit 601a51a

File tree

5 files changed

+91
-15
lines changed

5 files changed

+91
-15
lines changed

lib/SILOptimizer/Analysis/EscapeAnalysis.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ SILValue EscapeAnalysis::getPointerBase(SILValue value) {
122122
case ValueKind::StructElementAddrInst:
123123
case ValueKind::StructExtractInst:
124124
case ValueKind::TupleElementAddrInst:
125+
case ValueKind::InitExistentialAddrInst:
126+
case ValueKind::OpenExistentialAddrInst:
125127
case ValueKind::BeginAccessInst:
126128
case ValueKind::UncheckedTakeEnumDataAddrInst:
127129
case ValueKind::UncheckedEnumDataInst:
@@ -2165,9 +2167,7 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
21652167
}
21662168
case SILInstructionKind::RefElementAddrInst:
21672169
case SILInstructionKind::RefTailAddrInst:
2168-
case SILInstructionKind::ProjectBoxInst:
2169-
case SILInstructionKind::InitExistentialAddrInst:
2170-
case SILInstructionKind::OpenExistentialAddrInst: {
2170+
case SILInstructionKind::ProjectBoxInst: {
21712171
// For projections into objects, get the non-address reference operand and
21722172
// return an interior content node that the reference points to.
21732173
auto SVI = cast<SingleValueInstruction>(I);

test/SILOptimizer/escape_analysis.sil

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,9 +1019,8 @@ bb0(%0 : $Builtin.Int64, %1 : $X, %2 : $X, %3 : $X):
10191019

10201020
// CHECK-LABEL: CG of test_existential_addr
10211021
// CHECK-NEXT: Arg [ref] %0 Esc: A, Succ:
1022-
// CHECK-NEXT: Val %1 Esc: , Succ: (%2)
1023-
// CHECK-NEXT: Con %2 Esc: , Succ: (%2.1)
1024-
// CHECK-NEXT: Con [ref] %2.1 Esc: , Succ: %0
1022+
// CHECK-NEXT: Val %1 Esc: , Succ: (%1.1)
1023+
// CHECK-NEXT: Con [ref] %1.1 Esc: , Succ: %0
10251024
// CHECK-NEXT: End
10261025
sil @test_existential_addr : $@convention(thin) (@owned Pointer) -> () {
10271026
bb0(%0 : $Pointer):

test/SILOptimizer/escape_analysis_invalidate.sil

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
// RUN: %target-sil-opt %s -temp-rvalue-opt -enable-sil-verify-all -escapes-internal-verify | %FileCheck %s
1+
// RUN: %target-sil-opt %s -temp-rvalue-opt -enable-sil-verify-all -escapes-internal-verify -wmo | %FileCheck %s
22
//
33
// TempRValue iteratively uses EscapeAnalysis and deletes
44
// instructions. Make sure that the connection graph remains valid
55
// <rdar://57290845>.
6+
//
7+
// This test requires -wmo so EscapeAnalysis can find all
8+
// implementations of SomeProtocol.foo. Otherwise the existential
9+
// address appears to escape. As an alternative, we could be more
10+
// aggressive about considering address-type argument not to escape,
11+
// but that would require some limiting address_to_pointer to never
12+
// occur on an exclusive address argument.
613

714
import Swift
815

@@ -82,3 +89,24 @@ bb0(%0 : $*SomeProtocol, %1 : $SomeClass):
8289
%v = tuple ()
8390
return %v : $()
8491
}
92+
93+
sil hidden @$s26escape_analysis_invalidate12SomeInstanceV3fooyyF : $@convention(method) (SomeInstance) -> () {
94+
bb0(%0 : $SomeInstance):
95+
debug_value %0 : $SomeInstance, let, name "self", argno 1 // id: %1
96+
%2 = tuple () // user: %3
97+
return %2 : $() // id: %3
98+
}
99+
100+
sil private [transparent] [thunk] @$s26escape_analysis_invalidate12SomeInstanceVAA0A8ProtocolA2aDP3fooyyFTW : $@convention(witness_method: SomeProtocol) (@in_guaranteed SomeInstance) -> () {
101+
bb0(%0 : $*SomeInstance):
102+
%1 = load %0 : $*SomeInstance
103+
// function_ref SomeInstance.foo()
104+
%2 = function_ref @$s26escape_analysis_invalidate12SomeInstanceV3fooyyF : $@convention(method) (SomeInstance) -> ()
105+
%3 = apply %2(%1) : $@convention(method) (SomeInstance) -> ()
106+
%4 = tuple ()
107+
return %4 : $()
108+
}
109+
110+
sil_witness_table hidden SomeInstance: SomeProtocol module t {
111+
method #SomeProtocol.foo!1: <Self where Self : SomeProtocol> (Self) -> () -> () : @$s26escape_analysis_invalidate12SomeInstanceVAA0A8ProtocolA2aDP3fooyyFTW // protocol witness for SomeProtocol.foo() in conformance SomeInstance
112+
}

test/SILOptimizer/escape_analysis_reduced.sil

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -499,17 +499,18 @@ bb2:
499499
}
500500

501501
// CHECK-LABEL: CG of testPendingMerge
502-
// CHECK-NEXT: Arg %0 Esc: A, Succ: (%22)
503-
// CHECK-NEXT: Arg [ref] %1 Esc: G, Succ: (%9.1)
502+
// CHECK-NEXT: Arg %0 Esc: A, Succ: (%0.1)
503+
// CHECK-NEXT: Con %0.1 Esc: A, Succ: %1, %4.1
504+
// CHECK-NEXT: Arg [ref] %1 Esc: A, Succ: (%9.1)
504505
// CHECK-NEXT: Val %2 Esc: , Succ: (%9)
505506
// CHECK-NEXT: Val %4 Esc: , Succ: (%4.1)
506-
// CHECK-NEXT: Con %4.1 Esc: A, Succ: (%9.1), %7, %9
507-
// CHECK-NEXT: Val %5 Esc: , Succ: (%7)
508-
// CHECK-NEXT: Con %7 Esc: A, Succ: (%9.1)
507+
// CHECK-NEXT: Con %4.1 Esc: A, Succ: (%9.1), %5.1, %9
508+
// CHECK-NEXT: Val %5 Esc: , Succ: (%5.1)
509+
// CHECK-NEXT: Con %5.1 Esc: A, Succ: %1
509510
// CHECK-NEXT: Con [ref] %9 Esc: A, Succ: (%9.1)
510-
// CHECK-NEXT: Con [int] %9.1 Esc: G, Succ: (%9.1), %1
511-
// CHECK-NEXT: Con %22 Esc: A, Succ: (%22.1)
512-
// CHECK-NEXT: Con %22.1 Esc: A, Succ: %1, %4.1
511+
// CHECK-NEXT: Con [int] %9.1 Esc: G, Succ: (%9.2)
512+
// CHECK-NEXT: Con %9.2 Esc: G, Succ: (%9.3)
513+
// CHECK-NEXT: Con %9.3 Esc: G, Succ:
513514
// CHECK-LABEL: End
514515
sil private @testPendingMerge : $@convention(thin) (@owned VariableNode) -> (@out ASTNode, @error Error) {
515516
bb0(%0 : $*ASTNode, %1 : $VariableNode):
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all -release-hoisting %s | %FileCheck %s
2+
// REQUIRES: CPU=x86_64
3+
// REQUIRES: OS=macosx
4+
5+
sil_stage canonical
6+
7+
import Builtin
8+
import Swift
9+
import SwiftShims
10+
11+
// Test <rdar://59559805> miscompile; use-after-free
12+
// Avoid hoisting strong_release of an existential
13+
// over a store to the value.
14+
protocol ArrayElementProtocol {}
15+
16+
struct ArrayElementStruct : ArrayElementProtocol {
17+
@_hasStorage @_hasInitialValue var dummy: Int { get set }
18+
}
19+
20+
// CHECK-LABEL: sil @testArrayStorageInitAfterFree : $@convention(thin) () -> () {
21+
// CHECK: [[ARRAY:%.*]] = alloc_ref [stack] [tail_elems $ArrayElementProtocol * undef : $Builtin.Word] $_ContiguousArrayStorage<ArrayElementProtocol>
22+
// CHECK: [[CAST:%.*]] = upcast [[ARRAY]] : $_ContiguousArrayStorage<ArrayElementProtocol> to $__ContiguousArrayStorageBase
23+
// CHECK: [[TAIL:%.*]] = ref_tail_addr [[CAST]] : $__ContiguousArrayStorageBase, $ArrayElementProtocol
24+
// CHECK: [[ADR:%.*]] = init_existential_addr [[TAIL]] : $*ArrayElementProtocol, $ArrayElementStruct
25+
// CHECK: store %{{.*}} to [[ADR]] : $*ArrayElementStruct
26+
// CHECK: strong_release [[ARRAY]] : $_ContiguousArrayStorage<ArrayElementProtocol>
27+
// CHECK-LABEL: } // end sil function 'testArrayStorageInitAfterFree'
28+
sil @testArrayStorageInitAfterFree : $@convention(thin) () -> () {
29+
bb0:
30+
%0 = alloc_ref [stack] [tail_elems $ArrayElementProtocol * undef : $Builtin.Word] $_ContiguousArrayStorage<ArrayElementProtocol>
31+
%1 = upcast %0 : $_ContiguousArrayStorage<ArrayElementProtocol> to $__ContiguousArrayStorageBase
32+
%2 = struct $_SwiftArrayBodyStorage (undef : $Int, undef : $UInt)
33+
%3 = struct $_ArrayBody (%2 : $_SwiftArrayBodyStorage)
34+
%4 = ref_element_addr %1 : $__ContiguousArrayStorageBase, #__ContiguousArrayStorageBase.countAndCapacity
35+
store %3 to %4 : $*_ArrayBody
36+
%6 = ref_tail_addr %1 : $__ContiguousArrayStorageBase, $ArrayElementProtocol
37+
%7 = value_to_bridge_object undef : $Builtin.Int64
38+
%8 = struct $_StringObject (undef : $UInt64, %7 : $Builtin.BridgeObject)
39+
%9 = struct $_StringGuts (%8 : $_StringObject)
40+
%10 = struct $String (%9 : $_StringGuts)
41+
%11 = struct $ArrayElementStruct (undef : $Int)
42+
%12 = init_existential_addr %6 : $*ArrayElementProtocol, $ArrayElementStruct
43+
store %11 to %12 : $*ArrayElementStruct
44+
strong_release %0 : $_ContiguousArrayStorage<ArrayElementProtocol>
45+
dealloc_ref [stack] %0 : $_ContiguousArrayStorage<ArrayElementProtocol>
46+
%16 = tuple ()
47+
return %16 : $()
48+
}

0 commit comments

Comments
 (0)