Skip to content

Commit 95c142a

Browse files
committed
[SILGen] Fix leak in objc async thunk.
Fix a leak when emitting the native to foreign thunk for an async function which fulfills an Objective-C protocol requirement which can be fulfilled with either a value or an error via a nullable completion. Previously, the SIL in question used to look like this: ```sil %maybe_completion = ... try_apply %impl..., normal success, ... success(%value): switch_enum %maybe_completion... case some!enumelt: invoke case none!enumelt: ignore ignore: br join invoke(%completion): %some_value = enum Optional, some!enumelt, %value // consumes %value %guaranteed_some_value = begin_borrow %some_value %none_error = enum Optional, none!enumelt apply %completion(%guaranteed_some_value, %none_error) end_borrow %guaranteed_some_value destroy_value %some_value br join join: destroy_value %maybe_completion ... ``` which leaks %value on the codepath through `ignore`. Note that `%value` is consumed by the `enum` instruction, but `%completion` is invoked with `%guaranteed_some_value`, a guaranteed value. So there is no need to consume %value in `invoke`. Here, `%value` itself is borrowed and forwarded into an enum instruction whose result is passed to `%completion`: ```sil %maybe_completion = ... try_apply %impl..., normal success, ... success(%value): switch_enum %maybe_completion... case some!enumelt: invoke case none!enumelt: ignore ignore: br join invoke(%completion): %guaranteed_value = begin_borrow %value %guaranteed_some_value = enum Optional, some!enumelt, %guaranteed_value %none_error = enum Optional, none!enumelt apply %completion(%guaranteed_some_value, %none_error) end_borrow %guaranteed_some_value br join join: destroy_value %maybe_completion destroy_value %value ... ``` Because an argument scope was already being created and a cleanup was already being pushed for `%value`, nothing more is required to fix the issue than to reorder the enum and the borrow. rdar://119732084
1 parent eda8f15 commit 95c142a

File tree

4 files changed

+75
-2
lines changed

4 files changed

+75
-2
lines changed

lib/SILGen/SILGenBridging.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,10 +1773,10 @@ void SILGenFunction::emitNativeToForeignThunk(SILDeclRef thunk) {
17731773
SILParameterInfo param) {
17741774
auto bridgedTy = param.getInterfaceType();
17751775
auto bridgedArg = emitNativeToBridgedValue(loc,
1776-
arg, nativeFormalTy,
1776+
arg.borrow(*this, loc), nativeFormalTy,
17771777
bridgedTy,
17781778
SILType::getPrimitiveObjectType(bridgedTy));
1779-
completionHandlerArgs.push_back(bridgedArg.borrow(*this, loc).getValue());
1779+
completionHandlerArgs.push_back(bridgedArg.getValue());
17801780
};
17811781

17821782
Scope completionArgDestructureScope(*this, loc);

test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ typedef void (^NonsendableCompletionHandler)(NSString * _Nullable, NSString * _N
8282
-(void)doSomethingSlowNullably:(NSString *)operation completionHandler:(void (^ _Nullable)(NSInteger))handler;
8383
-(void)findAnswerNullably:(NSString *)operation completionHandler:(void (^ _Nullable)(NSString *))handler;
8484
-(void)doSomethingDangerousNullably:(NSString *)operation completionHandler:(void (^ _Nullable)(NSString *_Nullable, NSError *_Nullable))handler;
85+
-(void)doSomethingUnspecifiedNullablyWithCompletionHandler:(void (^ _Nullable)(NSString *_Nullable, NSError *_Nullable))handler;
8586

8687
// rdar://72604599
8788
- (void)stopRecordingWithHandler:(nullable void (^)(NSObject *_Nullable_result x, NSError *_Nullable error))handler __attribute__((swift_async_name("stopRecording()"))) __attribute__((swift_async(not_swift_private, 1)));

test/SILGen/objc_async_from_swift.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ class SlowServerlet: SlowServer {
127127
return x
128128
}
129129

130+
// CHECK-LABEL: sil{{.*}}13SlowServerlet{{.*}}30doSomethingUnspecifiedNullably{{.*}} :
131+
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none
132+
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
133+
override func doSomethingUnspecifiedNullably() async throws -> String {
134+
fatalError()
135+
}
136+
130137
// CHECK-LABEL: sil{{.*}}13SlowServerlet{{.*}}17doSomethingFlaggy{{.*}} :
131138
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none
132139
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
// RUN: %target-swift-emit-silgen -module-name Conformance -I %t %t/Conformance.swift | %FileCheck %s
4+
// REQUIRES: objc_interop
5+
6+
//--- module.modulemap
7+
module ObjCProto {
8+
header "objc_proto.h"
9+
export *
10+
}
11+
12+
//--- objc_proto.h
13+
#import <Foundation/Foundation.h>
14+
15+
@protocol Doable <NSObject>
16+
17+
- (void)doitWithCompletion:(void (^ __nullable)(NSObject * __nullable result, NSError * __nullable error))completion;
18+
19+
@end
20+
21+
//--- Conformance.swift
22+
import ObjCProto
23+
24+
public final class ConformsToProto: NSObject {}
25+
26+
extension ConformsToProto: Doable {
27+
// CHECK-LABEL: sil shared [thunk] [ossa] @$s11Conformance15ConformsToProtoC4doitSo8NSObjectCyYaKFyyYacfU_To : {{.*}} {
28+
// CHECK: {{bb[0-9]+}}([[UNOWNED_MAYBE_COMPLETION:%[^,]+]] :
29+
// CHECK: [[MAYBE_COMPLETION:%[^,]+]] = copy_block [[UNOWNED_MAYBE_COMPLETION]]
30+
// CHECK: try_apply {{.*}} normal [[SUCCESS:bb[0-9]+]], error [[FAILURE:bb[0-9]+]]
31+
// CHECK: [[SUCCESS]]([[RESULT:%[^,]+]] :
32+
// CHECK: [[GUARANTEED_MAYBE_COMPLETION_SUCCESS:%[^,]+]] = begin_borrow [[MAYBE_COMPLETION]]
33+
// CHECK: switch_enum [[GUARANTEED_MAYBE_COMPLETION_SUCCESS]]
34+
// CHECK-SAME: case #Optional.some!enumelt: [[SUCCESS_AND_COMPLETION:bb[0-9]+]]
35+
// CHECK-SAME: case #Optional.none!enumelt: [[SUCCESS_NO_COMPLETION:bb[0-9]+]]
36+
// CHECK: [[SUCCESS_NO_COMPLETION]]:
37+
// CHECK: br [[SUCCESS_JOIN:bb[0-9]+]]
38+
// CHECK: [[SUCCESS_AND_COMPLETION]]
39+
// CHECK: br [[SUCCESS_JOIN]]
40+
// CHECK: [[SUCCESS_JOIN]]:
41+
// CHECK: end_borrow [[GUARANTEED_MAYBE_COMPLETION_SUCCESS]]
42+
// CHECK: destroy_value [[MAYBE_COMPLETION]]
43+
// CHECK: destroy_value [[RESULT]]
44+
// CHECK: br [[EXIT:bb[0-9]+]]
45+
// CHECK: [[FAILURE]]([[ERROR:%[^,]+]] :
46+
// CHECK: [[GUARANTEED_MAYBE_COMPLETION_FAILURE:%[^,]+]] = begin_borrow [[MAYBE_COMPLETION]]
47+
// CHECK: switch_enum [[GUARANTEED_MAYBE_COMPLETION_FAILURE]]
48+
// CHECK-SAME: case #Optional.some!enumelt: [[FAILURE_AND_COMPLETION:bb[0-9]+]]
49+
// CHECK-SAME: case #Optional.none!enumelt: [[FAILURE_NO_COMPLETION:bb[0-9]+]]
50+
// CHECK: [[FAILURE_NO_COMPLETION]]:
51+
// CHECK: br [[FAILURE_JOIN:bb[0-9]+]]
52+
// CHECK: [[FAILURE_AND_COMPLETION]]
53+
// CHECK: br [[FAILURE_JOIN]]
54+
// CHECK: [[FAILURE_JOIN]]:
55+
// CHECK: end_borrow [[GUARANTEED_MAYBE_COMPLETION_FAILURE]]
56+
// CHECK: destroy_value [[MAYBE_COMPLETION]]
57+
// CHECK: destroy_value [[ERROR]]
58+
// CHECK: br [[EXIT]]
59+
// CHECK: [[EXIT]]:
60+
// CHECK-LABEL: } // end sil function '$s11Conformance15ConformsToProtoC4doitSo8NSObjectCyYaKFyyYacfU_To'
61+
public func doit() async throws -> NSObject {
62+
fatalError()
63+
}
64+
}
65+

0 commit comments

Comments
 (0)