• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dart-lang / ffigen / 4784152981

24 Apr 2023 08:39AM UTC coverage: 92.363% (-7.0%) from 99.366%
4784152981

push

github

GitHub
Merge pull request #560 from dart-lang/stable-to-7-x

1780 of 1780 new or added lines in 41 files covered. (100.0%)

3229 of 3496 relevant lines covered (92.36%)

25.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.64
/lib/src/code_generator/objc_interface.dart
1
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4

5
import 'package:ffigen/src/code_generator.dart';
6
import 'package:logging/logging.dart';
7

8
import '../strings.dart' as strings;
9
import 'binding_string.dart';
10
import 'utils.dart';
11
import 'writer.dart';
12

13
// Class methods defined on NSObject that we don't want to copy to child objects
14
// by default.
15
const _excludedNSObjectClassMethods = {
16
  'allocWithZone:',
17
  'class',
18
  'conformsToProtocol:',
19
  'copyWithZone:',
20
  'debugDescription',
21
  'description',
22
  'hash',
23
  'initialize',
24
  'instanceMethodForSelector:',
25
  'instanceMethodSignatureForSelector:',
26
  'instancesRespondToSelector:',
27
  'isSubclassOfClass:',
28
  'load',
29
  'mutableCopyWithZone:',
30
  'poseAsClass:',
31
  'resolveClassMethod:',
32
  'resolveInstanceMethod:',
33
  'setVersion:',
34
  'superclass',
35
  'version',
36
};
37

38
final _logger = Logger('ffigen.code_generator.objc_interface');
6✔
39

40
class ObjCInterface extends BindingType {
41
  ObjCInterface? superType;
42
  final methods = <String, ObjCMethod>{};
43
  bool filled = false;
44

45
  final String lookupName;
46
  final ObjCBuiltInFunctions builtInFunctions;
47
  late final ObjCInternalGlobal _classObject;
48
  late final ObjCInternalGlobal _isKindOfClass;
49
  late final Func _isKindOfClassMsgSend;
50

51
  ObjCInterface({
2✔
52
    String? usr,
53
    required String originalName,
54
    String? name,
55
    String? lookupName,
56
    String? dartDoc,
57
    required this.builtInFunctions,
58
  })  : lookupName = lookupName ?? originalName,
59
        super(
2✔
60
          usr: usr,
61
          originalName: originalName,
62
          name: name ?? originalName,
63
          dartDoc: dartDoc,
64
        ) {
65
    builtInFunctions.registerInterface(this);
4✔
66
  }
67

68
  bool get isNSString => originalName == "NSString";
6✔
69
  bool get isNSData => originalName == "NSData";
6✔
70

71
  @override
2✔
72
  BindingString toBindingString(Writer w) {
73
    String paramsToString(List<ObjCMethodParam> params,
2✔
74
        {required bool isStatic}) {
75
      final List<String> stringParams = [];
2✔
76

77
      if (isStatic) {
78
        stringParams.add('${w.className} _lib');
6✔
79
      }
80
      stringParams.addAll(params.map((p) =>
6✔
81
          (_getConvertedType(p.type, w, name) +
8✔
82
              (p.isNullable ? "? " : " ") +
4✔
83
              p.name)));
2✔
84
      return '(${stringParams.join(", ")})';
4✔
85
    }
86

87
    final s = StringBuffer();
2✔
88
    if (dartDoc != null) {
2✔
89
      s.write(makeDartDoc(dartDoc!));
×
90
    }
91

92
    final uniqueNamer = UniqueNamer({name, '_id', '_lib'});
8✔
93
    final natLib = w.className;
2✔
94

95
    builtInFunctions.ensureUtilsExist(w, s);
4✔
96
    final objType = PointerType(objCObjectType).getCType(w);
6✔
97

98
    // Class declaration.
99
    s.write('''
2✔
100
class $name extends ${superType?.name ?? '_ObjCWrapper'} {
6✔
101
  $name._($objType id, $natLib lib,
2✔
102
      {bool retain = false, bool release = false}) :
103
          super._(id, lib, retain: retain, release: release);
104

105
  /// Returns a [$name] that points to the same underlying object as [other].
2✔
106
  static $name castFrom<T extends _ObjCWrapper>(T other) {
2✔
107
    return $name._(other._id, other._lib, retain: true, release: true);
2✔
108
  }
109

110
  /// Returns a [$name] that wraps the given raw object pointer.
2✔
111
  static $name castFromPointer($natLib lib, $objType other,
2✔
112
      {bool retain = false, bool release = false}) {
113
    return $name._(other, lib, retain: retain, release: release);
2✔
114
  }
115

116
  /// Returns whether [obj] is an instance of [$name].
2✔
117
  static bool isInstance(_ObjCWrapper obj) {
118
    return obj._lib.${_isKindOfClassMsgSend.name}(
4✔
119
        obj._id, obj._lib.${_isKindOfClass.name},
4✔
120
        obj._lib.${_classObject.name});
4✔
121
  }
122

123
''');
2✔
124

125
    if (isNSString) {
2✔
126
      builtInFunctions.generateNSStringUtils(w, s);
4✔
127
    }
128

129
    // Methods.
130
    for (final m in methods.values) {
6✔
131
      final methodName = m._getDartMethodName(uniqueNamer);
2✔
132
      final isStatic = m.isClass;
2✔
133
      final returnType = m.returnType;
2✔
134

135
      // The method declaration.
136
      if (m.dartDoc != null) {
2✔
137
        s.write(makeDartDoc(m.dartDoc!));
×
138
      }
139

140
      s.write('  ');
2✔
141
      if (isStatic) {
142
        s.write('static ');
2✔
143
        s.write(
2✔
144
            _getConvertedReturnType(returnType, w, name, m.isNullableReturn));
6✔
145

146
        switch (m.kind) {
2✔
147
          case ObjCMethodKind.method:
2✔
148
            // static returnType methodName(NativeLibrary _lib, ...)
149
            s.write(' $methodName');
4✔
150
            break;
151
          case ObjCMethodKind.propertyGetter:
2✔
152
            // static returnType getMethodName(NativeLibrary _lib)
153
            s.write(' get');
2✔
154
            s.write(methodName[0].toUpperCase() + methodName.substring(1));
10✔
155
            break;
156
          case ObjCMethodKind.propertySetter:
2✔
157
            // static void setMethodName(NativeLibrary _lib, ...)
158
            s.write(' set');
2✔
159
            s.write(methodName[0].toUpperCase() + methodName.substring(1));
10✔
160
            break;
161
        }
162
        s.write(paramsToString(m.params, isStatic: true));
6✔
163
      } else {
164
        if (superType?.methods[m.originalName]?.sameAs(m) ?? false) {
10✔
165
          s.write('@override\n  ');
2✔
166
        }
167
        switch (m.kind) {
2✔
168
          case ObjCMethodKind.method:
2✔
169
            // returnType methodName(...)
170
            s.write(_getConvertedReturnType(
4✔
171
                returnType, w, name, m.isNullableReturn));
4✔
172
            s.write(' $methodName');
4✔
173
            s.write(paramsToString(m.params, isStatic: false));
6✔
174
            break;
175
          case ObjCMethodKind.propertyGetter:
2✔
176
            // returnType get methodName
177
            s.write(_getConvertedReturnType(
4✔
178
                returnType, w, name, m.isNullableReturn));
4✔
179
            s.write(' get $methodName');
4✔
180
            break;
181
          case ObjCMethodKind.propertySetter:
2✔
182
            // set methodName(...)
183
            s.write(' set $methodName');
4✔
184
            s.write(paramsToString(m.params, isStatic: false));
6✔
185
            break;
186
        }
187
      }
188

189
      s.write(' {\n');
2✔
190

191
      // Implementation.
192
      final convertReturn = m.kind != ObjCMethodKind.propertySetter &&
4✔
193
          _needsConverting(returnType);
2✔
194

195
      if (returnType != NativeType(SupportedNativeType.Void)) {
4✔
196
        s.write('    ${convertReturn ? 'final _ret = ' : 'return '}');
4✔
197
      }
198
      s.write('_lib.${m.msgSend!.name}(');
8✔
199
      s.write(isStatic ? '_lib.${_classObject.name}' : '_id');
8✔
200
      s.write(', _lib.${m.selObject!.name}');
8✔
201
      for (final p in m.params) {
4✔
202
        s.write(', ${_doArgConversion(p)}');
6✔
203
      }
204
      s.write(');\n');
2✔
205
      if (convertReturn) {
206
        final result = _doReturnConversion(returnType, '_ret', name, '_lib',
4✔
207
            m.isNullableReturn, m.isOwnedReturn);
4✔
208
        s.write('    return $result;');
4✔
209
      }
210

211
      s.write('  }\n\n');
2✔
212
    }
213

214
    s.write('}\n\n');
2✔
215

216
    if (isNSString) {
2✔
217
      builtInFunctions.generateStringUtils(w, s);
4✔
218
    }
219

220
    return BindingString(
2✔
221
        type: BindingStringType.objcInterface, string: s.toString());
2✔
222
  }
223

224
  @override
2✔
225
  void addDependencies(Set<Binding> dependencies) {
226
    if (dependencies.contains(this)) return;
2✔
227
    dependencies.add(this);
2✔
228
    builtInFunctions.addDependencies(dependencies);
4✔
229

230
    _classObject = ObjCInternalGlobal(
4✔
231
        '_class_$originalName',
4✔
232
        (Writer w) => '${builtInFunctions.getClass.name}("$lookupName")',
12✔
233
        builtInFunctions.getClass)
4✔
234
      ..addDependencies(dependencies);
2✔
235
    _isKindOfClass = builtInFunctions.getSelObject('isKindOfClass:');
6✔
236
    _isKindOfClassMsgSend = builtInFunctions.getMsgSendFunc(
6✔
237
        BooleanType(), [ObjCMethodParam(PointerType(objCObjectType), 'clazz')]);
10✔
238

239
    if (isNSString) {
2✔
240
      _addNSStringMethods();
2✔
241
    }
242

243
    if (isNSData) {
2✔
244
      _addNSDataMethods();
2✔
245
    }
246

247
    if (superType != null) {
2✔
248
      superType!.addDependencies(dependencies);
4✔
249
      _copyClassMethodsFromSuperType();
2✔
250
    }
251

252
    for (final m in methods.values) {
6✔
253
      m.addDependencies(dependencies, builtInFunctions);
4✔
254
    }
255
  }
256

257
  void _copyClassMethodsFromSuperType() {
2✔
258
    // Copy class methods from the super type, because Dart classes don't
259
    // inherit static methods.
260
    for (final m in superType!.methods.values) {
8✔
261
      if (m.isClass &&
2✔
262
          !_excludedNSObjectClassMethods.contains(m.originalName)) {
4✔
263
        addMethod(m);
2✔
264
      }
265
    }
266
  }
267

268
  void addMethod(ObjCMethod method) {
2✔
269
    final oldMethod = methods[method.originalName];
6✔
270
    if (oldMethod != null) {
271
      // Typically we ignore duplicate methods. However, property setters and
272
      // getters are duplicated in the AST. One copy is marked with
273
      // ObjCMethodKind.propertyGetter/Setter. The other copy is missing
274
      // important information, and is a plain old instanceMethod. So if the
275
      // existing method is an instanceMethod, and the new one is a property,
276
      // override it.
277
      if (method.isProperty && !oldMethod.isProperty) {
4✔
278
        // Fallthrough.
279
      } else if (!method.isProperty && oldMethod.isProperty) {
4✔
280
        // Don't override, but also skip the same method check below.
281
        return;
282
      } else {
283
        // Check duplicate is the same method.
284
        if (!method.sameAs(oldMethod)) {
2✔
285
          _logger.severe('Duplicate methods with different signatures: '
6✔
286
              '$originalName.${method.originalName}');
4✔
287
        }
288
        return;
289
      }
290
    }
291
    methods[method.originalName] = method;
6✔
292
  }
293

294
  void _addNSStringMethods() {
2✔
295
    addMethod(ObjCMethod(
4✔
296
      originalName: 'stringWithCharacters:length:',
297
      kind: ObjCMethodKind.method,
298
      isClass: true,
299
      returnType: this,
300
      params_: [
2✔
301
        ObjCMethodParam(PointerType(wCharType), 'characters'),
6✔
302
        ObjCMethodParam(unsignedIntType, 'length'),
4✔
303
      ],
304
    ));
305
    addMethod(ObjCMethod(
4✔
306
      originalName: 'dataUsingEncoding:',
307
      kind: ObjCMethodKind.method,
308
      isClass: false,
309
      returnType: builtInFunctions.nsData,
4✔
310
      params_: [
2✔
311
        ObjCMethodParam(unsignedIntType, 'encoding'),
4✔
312
      ],
313
    ));
314
    addMethod(ObjCMethod(
4✔
315
      originalName: 'length',
316
      kind: ObjCMethodKind.propertyGetter,
317
      isClass: false,
318
      returnType: unsignedIntType,
2✔
319
      params_: [],
2✔
320
    ));
321
  }
322

323
  void _addNSDataMethods() {
2✔
324
    addMethod(ObjCMethod(
4✔
325
      originalName: 'bytes',
326
      kind: ObjCMethodKind.propertyGetter,
327
      isClass: false,
328
      returnType: PointerType(voidType),
4✔
329
      params_: [],
2✔
330
    ));
331
  }
332

333
  @override
2✔
334
  String getCType(Writer w) => PointerType(objCObjectType).getCType(w);
6✔
335

336
  bool _isObject(Type type) =>
2✔
337
      type is PointerType && type.child == objCObjectType;
8✔
338

339
  bool _isInstanceType(Type type) =>
2✔
340
      type is Typealias &&
2✔
341
      type.originalName == strings.objcInstanceType &&
4✔
342
      _isObject(type.type);
4✔
343

344
  // Utils for converting between the internal types passed to native code, and
345
  // the external types visible to the user. For example, ObjCInterfaces are
346
  // passed to native as Pointer<ObjCObject>, but the user sees the Dart wrapper
347
  // class. These methods need to be kept in sync.
348
  bool _needsConverting(Type type) =>
2✔
349
      type is ObjCInterface ||
2✔
350
      type is ObjCBlock ||
2✔
351
      _isObject(type) ||
2✔
352
      _isInstanceType(type);
2✔
353

354
  String _getConvertedType(Type type, Writer w, String enclosingClass) {
2✔
355
    if (type is BooleanType) return 'bool';
2✔
356
    if (type is ObjCInterface) return type.name;
4✔
357
    if (type is ObjCBlock) return type.name;
4✔
358
    if (_isObject(type)) return 'NSObject';
2✔
359
    if (_isInstanceType(type)) return enclosingClass;
2✔
360
    return type.getDartType(w);
2✔
361
  }
362

363
  String _getConvertedReturnType(
2✔
364
      Type type, Writer w, String enclosingClass, bool isNullableReturn) {
365
    final result = _getConvertedType(type, w, enclosingClass);
2✔
366
    if (isNullableReturn) {
367
      return "$result?";
2✔
368
    }
369
    return result;
370
  }
371

372
  String _doArgConversion(ObjCMethodParam arg) {
2✔
373
    if (arg.type is ObjCInterface ||
4✔
374
        _isObject(arg.type) ||
4✔
375
        _isInstanceType(arg.type) ||
4✔
376
        arg.type is ObjCBlock) {
4✔
377
      if (arg.isNullable) {
2✔
378
        return '${arg.name}?._id ?? ffi.nullptr';
4✔
379
      } else {
380
        return '${arg.name}._id';
4✔
381
      }
382
    }
383
    return arg.name;
2✔
384
  }
385

386
  String _doReturnConversion(Type type, String value, String enclosingClass,
2✔
387
      String library, bool isNullable, bool isOwnedReturn) {
388
    final prefix = isNullable ? '$value.address == 0 ? null : ' : '';
2✔
389
    final ownerFlags = 'retain: ${!isOwnedReturn}, release: true';
2✔
390
    if (type is ObjCInterface) {
2✔
391
      return '$prefix${type.name}._($value, $library, $ownerFlags)';
4✔
392
    }
393
    if (type is ObjCBlock) {
2✔
394
      return '$prefix${type.name}._($value, $library)';
4✔
395
    }
396
    if (_isObject(type)) {
2✔
397
      return '${prefix}NSObject._($value, $library, $ownerFlags)';
2✔
398
    }
399
    if (_isInstanceType(type)) {
2✔
400
      return '$prefix$enclosingClass._($value, $library, $ownerFlags)';
2✔
401
    }
402
    return prefix + value;
×
403
  }
404
}
405

406
enum ObjCMethodKind {
407
  method,
408
  propertyGetter,
409
  propertySetter,
410
}
411

412
class ObjCProperty {
413
  final String originalName;
414
  String? dartName;
415

416
  ObjCProperty(this.originalName);
2✔
417
}
418

419
class ObjCMethod {
420
  final String? dartDoc;
421
  final String originalName;
422
  final ObjCProperty? property;
423
  final Type returnType;
424
  final bool isNullableReturn;
425
  final List<ObjCMethodParam> params;
426
  final ObjCMethodKind kind;
427
  final bool isClass;
428
  bool returnsRetained = false;
429
  ObjCInternalGlobal? selObject;
430
  Func? msgSend;
431

432
  ObjCMethod({
2✔
433
    required this.originalName,
434
    this.property,
435
    this.dartDoc,
436
    required this.kind,
437
    required this.isClass,
438
    required this.returnType,
439
    this.isNullableReturn = false,
440
    List<ObjCMethodParam>? params_,
441
  }) : params = params_ ?? [];
2✔
442

443
  bool get isProperty =>
2✔
444
      kind == ObjCMethodKind.propertyGetter ||
4✔
445
      kind == ObjCMethodKind.propertySetter;
4✔
446

447
  void addDependencies(
2✔
448
      Set<Binding> dependencies, ObjCBuiltInFunctions builtInFunctions) {
449
    returnType.addDependencies(dependencies);
4✔
450
    for (final p in params) {
4✔
451
      p.type.addDependencies(dependencies);
4✔
452
    }
453
    selObject ??= builtInFunctions.getSelObject(originalName)
6✔
454
      ..addDependencies(dependencies);
2✔
455
    msgSend ??= builtInFunctions.getMsgSendFunc(returnType, params)
8✔
456
      ..addDependencies(dependencies);
2✔
457
  }
458

459
  String _getDartMethodName(UniqueNamer uniqueNamer) {
2✔
460
    if (property != null) {
2✔
461
      // A getter and a setter are allowed to have the same name, so we can't
462
      // just run the name through uniqueNamer. Instead they need to share
463
      // the dartName, which is run through uniqueNamer.
464
      if (property!.dartName == null) {
4✔
465
        property!.dartName = uniqueNamer.makeUnique(property!.originalName);
10✔
466
      }
467
      return property!.dartName!;
4✔
468
    }
469
    // Objective C methods can look like:
470
    // foo
471
    // foo:
472
    // foo:someArgName:
473
    // So replace all ':' with '_'.
474
    return uniqueNamer.makeUnique(originalName.replaceAll(":", "_"));
6✔
475
  }
476

477
  bool sameAs(ObjCMethod other) {
2✔
478
    if (originalName != other.originalName) return false;
6✔
479
    if (isNullableReturn != other.isNullableReturn) return false;
6✔
480
    if (kind != other.kind) return false;
6✔
481
    if (isClass != other.isClass) return false;
6✔
482
    // msgSend is deduped by signature, so this check covers the signature.
483
    return msgSend == other.msgSend;
6✔
484
  }
485

486
  static final _copyRegExp = RegExp('[cC]opy');
6✔
487
  bool get isOwnedReturn =>
2✔
488
      returnsRetained ||
2✔
489
      originalName.startsWith('new') ||
4✔
490
      originalName.startsWith('alloc') ||
4✔
491
      originalName.contains(_copyRegExp);
6✔
492
}
493

494
class ObjCMethodParam {
495
  final Type type;
496
  final String name;
497
  final bool isNullable;
498
  ObjCMethodParam(this.type, this.name, {this.isNullable = false});
2✔
499
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc