Skip to content

Commit 31dcb84

Browse files
authored
[ffigen] Stop using NSProxy (#2029)
1 parent 2965c48 commit 31dcb84

28 files changed

+1020
-649
lines changed

pkgs/ffigen/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
allows a block or protocol to keep its owner isolate alive.
1111
- __Breaking change__: `keepIsolateAlive` defaults to true, so all existing ObjC
1212
blocks and protocols now keep their isolates alive by default.
13+
- Change how protocols are implemented to fix
14+
[a bug](https://github.com/dart-lang/http/issues/1702), by removing all uses
15+
of `NSProxy`.
1316

1417
## 17.0.0
1518

pkgs/ffigen/lib/src/code_generator/objc_block.dart

+50-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class ObjCBlock extends BindingType {
1515
final List<Parameter> params;
1616
final bool returnsRetained;
1717
ObjCBlockWrapperFuncs? _blockWrappers;
18+
ObjCProtocolMethodTrampoline? protocolTrampoline;
1819

1920
factory ObjCBlock({
2021
required Type returnType,
@@ -64,6 +65,10 @@ class ObjCBlock extends BindingType {
6465
}
6566
}
6667

68+
void fillProtocolTrampoline() {
69+
protocolTrampoline ??= builtInFunctions.getProtocolMethodTrampoline(this);
70+
}
71+
6772
// Generates a human readable name for the block based on the args and return
6873
// type. These names will be pretty verbose and unweildy, but they're at least
6974
// sensible and stable. Users can always add their own typedef with a simpler
@@ -346,6 +351,16 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>()
346351

347352
@override
348353
BindingString? toObjCBindingString(Writer w) {
354+
final chunks = [
355+
_blockWrappersBindingString(w),
356+
_protocolTrampolineBindingString(w),
357+
].nonNulls;
358+
if (chunks.isEmpty) return null;
359+
return BindingString(
360+
type: BindingStringType.objcBlock, string: chunks.join(''));
361+
}
362+
363+
String? _blockWrappersBindingString(Writer w) {
349364
if (_blockWrappers?.objCBindingsGenerated ?? true) return null;
350365
_blockWrappers!.objCBindingsGenerated = true;
351366

@@ -375,8 +390,7 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>()
375390
final blockingName =
376391
w.objCLevelUniqueNamer.makeUnique('_BlockingTrampoline');
377392

378-
final s = StringBuffer();
379-
s.write('''
393+
return '''
380394
381395
typedef ${returnType.getNativeType()} (^$listenerName)($argStr);
382396
__attribute__((visibility("default"))) __attribute__((used))
@@ -405,9 +419,39 @@ $listenerName $blockingWrapper(
405419
}
406420
};
407421
}
408-
''');
409-
return BindingString(
410-
type: BindingStringType.objcBlock, string: s.toString());
422+
''';
423+
}
424+
425+
String? _protocolTrampolineBindingString(Writer w) {
426+
if (protocolTrampoline?.objCBindingsGenerated ?? true) return null;
427+
protocolTrampoline!.objCBindingsGenerated = true;
428+
429+
final argsReceived = <String>[];
430+
final argsPassed = <String>[];
431+
for (var i = 0; i < params.length; ++i) {
432+
final param = params[i];
433+
final argName = i == 0 ? 'sel' : 'arg$i';
434+
argsReceived.add(param.getNativeType(varName: argName));
435+
argsPassed.add(argName);
436+
}
437+
438+
final ret = returnType.getNativeType();
439+
final argRecv = argsReceived.join(', ');
440+
final argPass = argsPassed.join(', ');
441+
final fnName = protocolTrampoline!.func.name;
442+
final block = w.objCLevelUniqueNamer.makeUnique('_ProtocolTrampoline');
443+
final msgSend = '((id (*)(id, SEL, SEL))objc_msgSend)';
444+
final getterSel = '@selector(getDOBJCDartProtocolMethodForSelector:)';
445+
final blkGetter = '(($block)$msgSend(target, $getterSel, sel))';
446+
447+
return '''
448+
449+
typedef $ret (^$block)($argRecv);
450+
__attribute__((visibility("default"))) __attribute__((used))
451+
$ret $fnName(id target, $argRecv) {
452+
return $blkGetter($argPass);
453+
}
454+
''';
411455
}
412456

413457
@override
@@ -465,6 +509,7 @@ $listenerName $blockingWrapper(
465509
visitor.visit(returnType);
466510
visitor.visitAll(params);
467511
visitor.visit(_blockWrappers);
512+
visitor.visit(protocolTrampoline);
468513
}
469514

470515
@override

pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart

+47-13
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ class ObjCBuiltInFunctions {
4444
static const protocolListenableMethod =
4545
ObjCImport('ObjCProtocolListenableMethod');
4646
static const protocolBuilder = ObjCImport('ObjCProtocolBuilder');
47-
static const dartProxy = ObjCImport('DartProxy');
4847
static const unimplementedOptionalMethodException =
4948
ObjCImport('UnimplementedOptionalMethodException');
5049
static const checkOsVersion = ObjCImport('checkOsVersion');
@@ -54,8 +53,8 @@ class ObjCBuiltInFunctions {
5453
@visibleForTesting
5554
static const builtInInterfaces = {
5655
'DartInputStreamAdapter',
57-
'DartProxy',
58-
'DartProxyBuilder',
56+
'DartProtocol',
57+
'DartProtocolBuilder',
5958
'NSArray',
6059
'NSCharacterSet',
6160
'NSCoder',
@@ -83,7 +82,6 @@ class ObjCBuiltInFunctions {
8382
'NSOrderedCollectionDifference',
8483
'NSOrderedSet',
8584
'NSOutputStream',
86-
'NSProxy',
8785
'NSRunLoop',
8886
'NSSet',
8987
'NSStream',
@@ -178,12 +176,9 @@ class ObjCBuiltInFunctions {
178176
ObjCMsgSendFunc getMsgSendFunc(Type returnType, List<Parameter> params) {
179177
params = _methodSigParams(params);
180178
returnType = _methodSigType(returnType);
181-
final id = _methodSigId(returnType, params);
179+
final (id, idHash) = _methodSigId(returnType, params);
182180
return _msgSendFuncs[id] ??= ObjCMsgSendFunc(
183-
'_objc_msgSend_${fnvHash32(id).toRadixString(36)}',
184-
returnType,
185-
params,
186-
useMsgSendVariants);
181+
'_objc_msgSend_$idHash', returnType, params, useMsgSendVariants);
187182
}
188183

189184
final _selObjects = <String, ObjCInternalGlobal>{};
@@ -194,7 +189,7 @@ class ObjCBuiltInFunctions {
194189
);
195190
}
196191

197-
String _methodSigId(Type returnType, List<Parameter> params) {
192+
(String, String) _methodSigId(Type returnType, List<Parameter> params) {
198193
final paramIds = <String>[];
199194
for (final p in params) {
200195
// The trampoline ID is based on the getNativeType of the param. Objects
@@ -205,7 +200,8 @@ class ObjCBuiltInFunctions {
205200
}
206201
final rt =
207202
returnType.getNativeType(varName: returnType.generateRetain('') ?? '');
208-
return '$rt,${paramIds.join(',')}';
203+
final id = '$rt,${paramIds.join(',')}';
204+
return (id, fnvHash32(id).toRadixString(36));
209205
}
210206

211207
Type _methodSigType(Type t) {
@@ -242,8 +238,7 @@ class ObjCBuiltInFunctions {
242238

243239
final _blockTrampolines = <String, ObjCBlockWrapperFuncs>{};
244240
ObjCBlockWrapperFuncs? getBlockTrampolines(ObjCBlock block) {
245-
final id = _methodSigId(block.returnType, block.params);
246-
final idHash = fnvHash32(id).toRadixString(36);
241+
final (id, idHash) = _methodSigId(block.returnType, block.params);
247242
return _blockTrampolines[id] ??= ObjCBlockWrapperFuncs(
248243
_blockTrampolineFunc('_${wrapperName}_wrapListenerBlock_$idHash'),
249244
_blockTrampolineFunc('_${wrapperName}_wrapBlockingBlock_$idHash',
@@ -285,6 +280,27 @@ class ObjCBuiltInFunctions {
285280
ffiNativeConfig: const FfiNativeConfig(enabled: true),
286281
);
287282

283+
final _protocolTrampolines = <String, ObjCProtocolMethodTrampoline>{};
284+
ObjCProtocolMethodTrampoline? getProtocolMethodTrampoline(ObjCBlock block) {
285+
final (id, idHash) = _methodSigId(block.returnType, block.params);
286+
return _protocolTrampolines[id] ??= ObjCProtocolMethodTrampoline(Func(
287+
name: '_${wrapperName}_protocolTrampoline_$idHash',
288+
returnType: block.returnType,
289+
parameters: [
290+
Parameter(
291+
name: 'target',
292+
type: PointerType(objCObjectType),
293+
objCConsumed: false),
294+
...block.params,
295+
],
296+
objCReturnsRetained: false,
297+
isLeaf: false,
298+
isInternal: true,
299+
useNameForLookup: true,
300+
ffiNativeConfig: const FfiNativeConfig(enabled: true),
301+
));
302+
}
303+
288304
static bool isInstanceType(Type type) {
289305
if (type is ObjCInstanceType) return true;
290306
final baseType = type.typealiasType;
@@ -308,6 +324,24 @@ class ObjCBlockWrapperFuncs extends AstNode {
308324
}
309325
}
310326

327+
/// A native trampoline function for a protocol method.
328+
class ObjCProtocolMethodTrampoline extends AstNode {
329+
final Func func;
330+
bool objCBindingsGenerated = false;
331+
332+
ObjCProtocolMethodTrampoline(this.func);
333+
334+
@override
335+
void visitChildren(Visitor visitor) {
336+
super.visitChildren(visitor);
337+
visitor.visit(func);
338+
}
339+
340+
@override
341+
void visit(Visitation visitation) =>
342+
visitation.visitObjCProtocolMethodTrampoline(this);
343+
}
344+
311345
/// A function, global variable, or helper type defined in package:objective_c.
312346
class ObjCImport {
313347
final String name;

pkgs/ffigen/lib/src/code_generator/objc_methods.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class ObjCMethod extends AstNode {
260260
],
261261
returnsRetained: returnsRetained,
262262
builtInFunctions: builtInFunctions,
263-
);
263+
)..fillProtocolTrampoline();
264264
}
265265

266266
String getDartMethodName(UniqueNamer uniqueNamer,

pkgs/ffigen/lib/src/code_generator/objc_protocol.dart

+17-3
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ interface class $name extends $protocolBase $impls{
164164
methodFields.write('''static final $fieldName = $methodClass<$funcType>(
165165
${_protocolPointer.name},
166166
${method.selObject.name},
167+
${_trampolineAddress(w, block)},
167168
$getSignature(
168169
${_protocolPointer.name},
169170
${method.selObject.name},
@@ -185,13 +186,15 @@ interface class $name extends $protocolBase $impls{
185186
/// If `\$keepIsolateAlive` is true, this protocol will keep this isolate
186187
/// alive until it is garbage collected by both Dart and ObjC.
187188
static $name implement($args) {
188-
final builder = $protocolBuilder();
189+
final builder = $protocolBuilder(debugName: '$originalName');
189190
$buildImplementations
190191
return $name.castFrom(builder.build(keepIsolateAlive: \$keepIsolateAlive));
191192
}
192193
193194
/// Adds the implementation of the $originalName protocol to an existing
194195
/// [$protocolBuilder].
196+
///
197+
/// Note: You cannot call this method after you have called `builder.build`.
195198
static void addToBuilder($protocolBuilder builder, $args) {
196199
$buildImplementations
197200
}
@@ -207,14 +210,16 @@ interface class $name extends $protocolBase $impls{
207210
/// If `\$keepIsolateAlive` is true, this protocol will keep this isolate
208211
/// alive until it is garbage collected by both Dart and ObjC.
209212
static $name implementAsListener($args) {
210-
final builder = $protocolBuilder();
213+
final builder = $protocolBuilder(debugName: '$originalName');
211214
$buildListenerImplementations
212215
return $name.castFrom(builder.build(keepIsolateAlive: \$keepIsolateAlive));
213216
}
214217
215218
/// Adds the implementation of the $originalName protocol to an existing
216219
/// [$protocolBuilder]. All methods that can be implemented as listeners will
217220
/// be.
221+
///
222+
/// Note: You cannot call this method after you have called `builder.build`.
218223
static void addToBuilderAsListener($protocolBuilder builder, $args) {
219224
$buildListenerImplementations
220225
}
@@ -226,14 +231,16 @@ interface class $name extends $protocolBase $impls{
226231
/// If `\$keepIsolateAlive` is true, this protocol will keep this isolate
227232
/// alive until it is garbage collected by both Dart and ObjC.
228233
static $name implementAsBlocking($args) {
229-
final builder = $protocolBuilder();
234+
final builder = $protocolBuilder(debugName: '$originalName');
230235
$buildBlockingImplementations
231236
return $name.castFrom(builder.build(keepIsolateAlive: \$keepIsolateAlive));
232237
}
233238
234239
/// Adds the implementation of the $originalName protocol to an existing
235240
/// [$protocolBuilder]. All methods that can be implemented as blocking
236241
/// listeners will be.
242+
///
243+
/// Note: You cannot call this method after you have called `builder.build`.
237244
static void addToBuilderAsBlocking($protocolBuilder builder, $args) {
238245
$buildBlockingImplementations
239246
}
@@ -264,6 +271,13 @@ interface class $name extends $protocolBase $impls{
264271
type: BindingStringType.objcProtocol, string: s.toString());
265272
}
266273

274+
static String _trampolineAddress(Writer w, ObjCBlock block) {
275+
final func = block.protocolTrampoline!.func;
276+
final type =
277+
NativeFunc(func.functionType).getCType(w, writeArgumentNames: false);
278+
return '${w.ffiLibraryPrefix}.Native.addressOf<$type>(${func.name}).cast()';
279+
}
280+
267281
@override
268282
BindingString? toObjCBindingString(Writer w) {
269283
final wrapName = builtInFunctions.wrapperName;

pkgs/ffigen/lib/src/code_generator/writer.dart

+1
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ class Writer {
429429
s.write('''
430430
#include <stdint.h>
431431
#import <Foundation/Foundation.h>
432+
#import <objc/message.h>
432433
''');
433434

434435
for (final entryPoint in nativeEntryPoints) {

pkgs/ffigen/lib/src/visitor/find_transitive_deps.dart

+6
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ class FindDirectTransitiveDepsVisitation extends Visitation {
7070
visitor.visitAll(node.superProtocols);
7171
}
7272

73+
@override
74+
void visitObjCProtocolMethodTrampoline(ObjCProtocolMethodTrampoline node) {
75+
// Don't visit transitive deps of ObjCProtocolMethodTrampoline, as it can
76+
// force include protocols that would otherwise be omitted.
77+
}
78+
7379
@override
7480
void visitBinding(Binding node) => _visitImpl(node, true);
7581
}

pkgs/ffigen/lib/src/visitor/visitor.dart

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ abstract class Visitation {
8383
void visitGlobal(Global node) => visitLookUpBinding(node);
8484
void visitTypealias(Typealias node) => visitBindingType(node);
8585
void visitPointerType(PointerType node) => visitType(node);
86+
void visitObjCProtocolMethodTrampoline(ObjCProtocolMethodTrampoline node) =>
87+
visitAstNode(node);
8688

8789
/// Default behavior for all visit methods.
8890
void visitAstNode(AstNode node) => node..visitChildren(visitor);

0 commit comments

Comments
 (0)