Skip to content

Commit 23696e4

Browse files
committed
Fix EscapeAnalysis::mayReleaseContent
The previous implementation made extremely subtle and specific assumptions about how the API is used which doesn't apply everywhere. It was trying very hard to avoid regressing performance relative to an even older implementation that didn't even try to consider deinitializer side effects. The aggressive logic was based on the idea that a release must have a corresponding retain somewhere in the same function and we don't care if the last release happens early if there are no more aliasing uses. All the unit tests I wrote previously were based on release hoisting, which happens to work given the way the API is used. But this logic is incorrect for retain sinking. In that case sinking past an "unrelated" release could cause the object to be freed early. See test/SILOptimizer/arc_crash.swift. With SemanticARC and other SIL improvements being made, I'm afraid bugs like this will begin to surface. To fix it, just remove the subtle logic to leave behind a simple and sound EscapeAnalysis API. To do better, we will need to rewrite the AliasAnalysis logic for release side effects, which is currently a tangled web. In the meantime, SemanticARC can handle many cases without EscapeAnalysis. Fixes rdar://74469299 (ARC miscompile: EscapeAnalysis::mayReleaseContent; potential use-after-free) While fixing this, add support for address-type queries too: Fixes rdar://74360041 (Assertion failed: (!releasedReference->getType().isAddress() && "an address is never a reference"), function mayReleaseContent
1 parent 2f584b5 commit 23696e4

File tree

5 files changed

+337
-63
lines changed

5 files changed

+337
-63
lines changed

include/swift/SILOptimizer/Analysis/EscapeAnalysis.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,10 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
11141114
bool canEscapeToUsePoint(SILValue value, SILInstruction *usePoint,
11151115
ConnectionGraph *conGraph);
11161116

1117+
/// Common implementation for mayReleaseReferenceContent and
1118+
/// mayReleaseAddressContent.
1119+
bool mayReleaseContent(SILValue releasedPtr, SILValue liveAddress);
1120+
11171121
friend struct ::CGForDotView;
11181122

11191123
public:
@@ -1163,8 +1167,38 @@ class EscapeAnalysis : public BottomUpIPAnalysis {
11631167
bool canEscapeTo(SILValue V, DestroyValueInst *DVI);
11641168

11651169
/// Return true if \p releasedReference deinitialization may release memory
1166-
/// pointed to by \p accessedAddress.
1167-
bool mayReleaseContent(SILValue releasedReference, SILValue accessedAddress);
1170+
/// pointed to by \p liveAddress.
1171+
///
1172+
/// This determines whether a direct release of \p releasedReference, such as
1173+
/// destroy_value or strong_release may release memory pointed to by \p
1174+
/// liveAddress. It can also be used to determine whether passing a
1175+
/// reference-type call argument may release \p liveAddress.
1176+
///
1177+
/// This does not distinguish between a call that releases \p
1178+
/// releasedReference directly, vs. a call that releases one of indirect
1179+
/// references.The side effects of releasing any object reachable from \p
1180+
/// releasedReference are a strict subset of the side effects of directly
1181+
/// releasing the parent reference.
1182+
bool mayReleaseReferenceContent(SILValue releasedReference,
1183+
SILValue liveAddress) {
1184+
assert(!releasedReference->getType().isAddress() &&
1185+
"expected a potentially nontrivial value, not an address");
1186+
return mayReleaseContent(releasedReference, liveAddress);
1187+
}
1188+
1189+
/// Return true if accessing memory at \p accessedAddress may release memory
1190+
/// pointed to by \p liveAddress.
1191+
///
1192+
/// This makes sense for determining whether accessing indirect call argument
1193+
/// \p accessedAddress may release memory pointed to by \p liveAddress.
1194+
///
1195+
/// "Access" to the memory can be any release of a reference pointed to by \p
1196+
/// accessedAddress, so '@in' and '@inout' are handled the same.
1197+
bool mayReleaseAddressContent(SILValue accessedAddress,
1198+
SILValue liveAddress) {
1199+
assert(accessedAddress->getType().isAddress() && "expected an address");
1200+
return mayReleaseContent(accessedAddress, liveAddress);
1201+
}
11681202

11691203
/// Returns true if the pointers \p V1 and \p V2 can possibly point to the
11701204
/// same memory.

lib/SILOptimizer/Analysis/AliasAnalysis.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -723,8 +723,18 @@ bool AliasAnalysis::canApplyDecrementRefCount(FullApplySite FAS, SILValue Ptr) {
723723
if (ArgEffect.mayRelease()) {
724724
// The function may release this argument, so check if the pointer can
725725
// escape to it.
726-
if (EA->mayReleaseContent(FAS.getArgument(Idx), Ptr))
727-
return true;
726+
auto arg = FAS.getArgument(Idx);
727+
if (arg->getType().isAddress()) {
728+
// Handle indirect argument as if they are a release to any references
729+
// pointed to by the argument's address.
730+
if (EA->mayReleaseAddressContent(arg, Ptr))
731+
return true;
732+
} else {
733+
// Handle direct arguments as if they are a direct release of the
734+
// reference (just like a destroy_value).
735+
if (EA->mayReleaseReferenceContent(arg, Ptr))
736+
return true;
737+
}
728738
}
729739
}
730740
return false;
@@ -744,7 +754,7 @@ bool AliasAnalysis::canBuiltinDecrementRefCount(BuiltinInst *BI, SILValue Ptr) {
744754
// to be an owned reference and disallows addresses. Conservatively handle
745755
// address type arguments as and conservatively treat all other values
746756
// potential owned references.
747-
if (Arg->getType().isAddress() || EA->mayReleaseContent(Arg, Ptr))
757+
if (Arg->getType().isAddress() || EA->mayReleaseReferenceContent(Arg, Ptr))
748758
return true;
749759
}
750760
return false;
@@ -788,7 +798,7 @@ bool AliasAnalysis::mayValueReleaseInterfereWithInstruction(
788798
// accessedPointer. Access to any objects beyond the first released refcounted
789799
// object are irrelevant--they must already have sufficient refcount that they
790800
// won't be released when releasing Ptr.
791-
return EA->mayReleaseContent(releasedReference, accessedPointer);
801+
return EA->mayReleaseReferenceContent(releasedReference, accessedPointer);
792802
}
793803

794804
void AliasAnalysis::initialize(SILPassManager *PM) {

lib/SILOptimizer/Analysis/EscapeAnalysis.cpp

Lines changed: 76 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2727,95 +2727,115 @@ bool EscapeAnalysis::canPointToSameMemory(SILValue V1, SILValue V2) {
27272727
return true;
27282728
}
27292729

2730-
// Return true if deinitialization of \p releasedReference may release memory
2731-
// directly pointed to by \p accessAddress.
2730+
// Returns true if deinitialization of \p releasedPtr may release memory
2731+
// directly pointed to by \p livePtr.
27322732
//
2733-
// Note that \p accessedAddress could be a reference itself, an address of a
2734-
// local/argument that contains a reference, or even a pointer to the middle of
2735-
// an object (even if it is an exclusive argument).
2736-
//
2737-
// This is almost the same as asking "is the content node for accessedAddress
2738-
// reachable via releasedReference", with three subtle differences:
2739-
//
2740-
// (1) A locally referenced object can only be freed when deinitializing
2741-
// releasedReference if it is the same object. Indirect references will be kept
2742-
// alive by their distinct local references--ARC can't remove those without
2743-
// inserting a mark_dependence/end_dependence scope.
2744-
//
2745-
// (2) the content of exclusive arguments may be indirectly reachable via
2746-
// releasedReference, but the exclusive argument must have it's own reference
2747-
// count, so cannot be freed via the locally released reference.
2733+
// The implementation is common between mayReleaseReferenceContent and
2734+
// mayReleaseAddressContent, but the semantics are different. For references,
2735+
// this models the release of the reference itself. For addresses, this models
2736+
// the release of any reference pointed to by the address. The caller should
2737+
// explicitly ask for the right one so they aren't surprised. Here we simply
2738+
// switch behavior based on whether \p releasedPtr is an address type.
27482739
//
2749-
// (3) Objects may contain raw pointers into themselves or into other
2750-
// objects. Any access to the raw pointer is not considered a use of the object
2751-
// because that access must be "guarded" by a fix_lifetime or
2752-
// mark_dependence/end_dependence that acts as a placeholder.
2740+
// Note that \p livePtr could be a reference itself, an address of a
2741+
// local/argument that contains a reference, or even a pointer to the middle of
2742+
// an object. (Even an exclusive argument may point to the middle of an object).
27532743
//
2754-
// There are two interesting cases in which a connection graph query can
2755-
// determine that the accessed memory cannot be released:
2744+
// This is similar to asking "is the content of livePtr reachable via
2745+
// releasedPtr". There are two interesting cases in which a connection graph
2746+
// query can determine that the accessed memory cannot be released:
27562747
//
2757-
// Case #1: accessedAddress points to a uniquely identified object that does not
2748+
// Case #1: \p livePtr points to a uniquely identified object that does not
27582749
// escape within this function.
27592750
//
2760-
// Note: A "uniquely identified object" is either a locally allocated object,
2761-
// which is obviously not reachable outside this function, or an exclusive
2762-
// address argument, which *is* reachable outside this function, but must
2763-
// have its own reference count so cannot be released locally.
2751+
// In this case, it is sufficient to ensure that no connection graph path exists
2752+
// from the content of \p livePtr to the content of \p releasedPtr.
2753+
//
2754+
// Note: A "uniquely identified object" is either locally allocated, which is
2755+
// obviously not reachable outside this function, or an exclusive address, which
2756+
// *is* reachable outside this function, but must have its own reference count
2757+
// so cannot be released in this function or its callees.
27642758
//
27652759
// Case #2: The released reference points to a local object and no connection
27662760
// graph path exists from the referenced object to a global-escaping or
2767-
// argument-escaping node without traversing a non-interior edge.
2761+
// argument-escaping node.
2762+
//
2763+
// TODO: This API is inneffective for release hoisting, because the release
2764+
// itself is often the only place that an object's contents may escape. We can't
2765+
// currently determine that since the contents cannot escape prior to \p
2766+
// releasePtr, then livePtr cannot possible point to the same memory!
27682767
//
2769-
// In both cases, the connection graph is sufficient to determine if the
2770-
// accessed content may be released. To prove that the accessed memory is
2771-
// distinct from any released memory it is now sufficient to check that no
2772-
// connection graph path exists from the released object's node to the accessed
2773-
// content node without traversing a non-interior edge.
2774-
bool EscapeAnalysis::mayReleaseContent(SILValue releasedReference,
2775-
SILValue accessedAddress) {
2776-
assert(!releasedReference->getType().isAddress()
2777-
&& "an address is never a reference");
2778-
2779-
SILFunction *f = getCommonFunction(releasedReference, accessedAddress);
2768+
// TODO: In the future, we may have an AliasAnalysis query that distinguishes
2769+
// between retain-sinking vs. release-hoisting. With SemanticARC, we may not
2770+
// need to do this, but it is possible to be much more aggressive with
2771+
// release-hoisting. This is becase, for a retain/release pair, it's always ok
2772+
// to release earlier as long as there are no subsequent aliasing uses. If the
2773+
// caller is only concerned with release hoisting and knows there are no
2774+
// subsequent aliasing uses protected by a local release, then the connection
2775+
// graph reachability check here only needs to search within the current object
2776+
// (it can stop at a non-interior edge). This would assume that any indirectly
2777+
// released reference needs to be kept alive by some distinct local
2778+
// references--ARC can't remove those without inserting a
2779+
// mark_dependence/end_dependence scope. It would also ignore the fact that
2780+
// objects may contain raw pointers into themselves or into other objects. Any
2781+
// access to the raw pointer is not considered a use of the object because that
2782+
// access must be "guarded" by a fix_lifetime or mark_dependence/end_dependence
2783+
// that acts as a placeholder.
2784+
bool EscapeAnalysis::mayReleaseContent(SILValue releasedPtr, SILValue livePtr) {
2785+
SILFunction *f = getCommonFunction(releasedPtr, livePtr);
27802786
if (!f)
27812787
return true;
27822788

27832789
auto *conGraph = getConnectionGraph(f);
27842790

2785-
CGNode *addrContentNode = conGraph->getValueContent(accessedAddress);
2786-
if (!addrContentNode)
2791+
CGNode *liveContentNode = conGraph->getValueContent(livePtr);
2792+
if (!liveContentNode)
27872793
return true;
27882794

2789-
// Case #1: Unique accessedAddress whose content does not escape.
2790-
bool isAccessUniq =
2791-
isUniquelyIdentified(accessedAddress)
2792-
&& !addrContentNode->valueEscapesInsideFunction(accessedAddress);
2793-
2794-
// Case #2: releasedReference points to a local object.
2795-
if (!isAccessUniq && !pointsToLocalObject(releasedReference))
2795+
// Case #1: Unique livePtr whose content does not escape.
2796+
//
2797+
// If \p livePtr is an exclusive function argument, it may be indirectly
2798+
// reachable via releasedPtr, but the exclusive argument must have it's own
2799+
// reference count retained by the called. We consider \p livePtr unique since
2800+
// it so cannot be freed via a release of \p releasedPtr within this function
2801+
// or its callees.
2802+
bool isLiveAddressUnique =
2803+
isUniquelyIdentified(livePtr)
2804+
&& !liveContentNode->valueEscapesInsideFunction(livePtr);
2805+
2806+
// Case #2: releasedPtr points to a local object.
2807+
if (!isLiveAddressUnique && !pointsToLocalObject(releasedPtr))
27962808
return true;
27972809

2798-
CGNode *releasedObjNode = conGraph->getValueContent(releasedReference);
2810+
// If \p releasedPtr is an address, then its released content is at least two
2811+
// levels away: the address points to a reference, which points to an object.
2812+
// CGNode *releasedObjNode = nullptr;
2813+
CGNode *releasedObjNode = nullptr;
2814+
if (releasedPtr->getType().isAddress()) {
2815+
CGNode *addrContentObjNode = conGraph->getValueContent(releasedPtr);
2816+
if (!addrContentObjNode)
2817+
return true;
2818+
releasedObjNode = conGraph->getOrCreateUnknownContent(addrContentObjNode);
2819+
} else {
2820+
releasedObjNode = conGraph->getValueContent(releasedPtr);
2821+
}
27992822
// Make sure we have at least one value CGNode for releasedReference.
28002823
if (!releasedObjNode)
28012824
return true;
28022825

2803-
// Check for reachability from releasedObjNode to addrContentNode.
2826+
// Check for reachability from releasedObjNode to liveContentNode.
28042827
// A pointsTo cycle is equivalent to a null pointsTo.
28052828
CGNodeWorklist worklist(conGraph);
28062829
for (CGNode *releasedNode = releasedObjNode;
28072830
releasedNode && worklist.tryPush(releasedNode);
28082831
releasedNode = releasedNode->getContentNodeOrNull()) {
28092832
// A path exists from released content to accessed content.
2810-
if (releasedNode == addrContentNode)
2833+
if (releasedNode == liveContentNode)
28112834
return true;
28122835

28132836
// A path exists to an escaping node.
2814-
if (!isAccessUniq && releasedNode->escapesInsideFunction())
2837+
if (!isLiveAddressUnique && releasedNode->escapesInsideFunction())
28152838
return true;
2816-
2817-
if (!releasedNode->isInterior())
2818-
break;
28192839
}
28202840
return false; // no path to escaping memory that may be freed.
28212841
}

test/SILOptimizer/arc_crash.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %target-swift-frontend -O %s -parse-as-library -emit-sil -enforce-exclusivity=none -Xllvm -sil-disable-pass=function-signature-opts | %FileCheck %s
2+
3+
// Test ARC optimizations on source level tests that have been
4+
// miscompiled and crash (e.g. because of use-after-free).
5+
6+
// -----------------------------------------------------------------------------
7+
// rdar://74469299 (ARC miscompile: EscapeAnalysis::mayReleaseContent;
8+
// potential use-after-free)
9+
// -----------------------------------------------------------------------------
10+
11+
public class Base {
12+
var i = 3
13+
init() {}
14+
}
15+
public class Node : Base {
16+
var node: Base
17+
18+
init(node: Base) { self.node = node }
19+
}
20+
struct Queue {
21+
var node: Node
22+
}
23+
24+
@inline(never)
25+
func useQueue(q: __owned Queue) {}
26+
27+
@inline(never)
28+
func useNode(n: Base) -> Int {
29+
return n.i
30+
}
31+
32+
// CHECK-LABEL: sil [noinline] @$s9arc_crash14testMayReleaseAA4BaseCyF : $@convention(thin) () -> @owned Base {
33+
// CHECK: [[BASE:%.*]] = alloc_ref $Base
34+
// CHECK: strong_retain [[BASE]] : $Base
35+
// CHECK: apply %{{.*}} : $@convention(thin) (@owned Queue) -> ()
36+
// CHECK-LABEL: } // end sil function '$s9arc_crash14testMayReleaseAA4BaseCyF'
37+
@inline(never)
38+
public func testMayRelease() -> Base {
39+
let n2 = Base()
40+
let n1 = Node(node: n2)
41+
let q = Queue(node: n1)
42+
// n2 must not be release before useQueue.
43+
useQueue(q: q)
44+
return n2
45+
}
46+
47+
// This crashes when testMayRelease releases the object too early.
48+
// print("Object:")
49+
// print(testMayRelease())
50+
// -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)