• 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

94.16
/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.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 'dart:ffi';
6

7
import 'package:ffigen/src/code_generator.dart';
8
import 'package:ffigen/src/header_parser/data.dart';
9
import 'package:logging/logging.dart';
10

11
import '../clang_bindings/clang_bindings.dart' as clang_types;
12
import '../includer.dart';
13
import '../utils.dart';
14

15
final _logger = Logger('ffigen.header_parser.objcinterfacedecl_parser');
6✔
16

17
Pointer<
18
        NativeFunction<
19
            Int32 Function(
20
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
21
    _parseInterfaceVisitorPtr;
22
Pointer<
23
        NativeFunction<
24
            Int32 Function(
25
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
26
    _isClassDeclarationVisitorPtr;
27
Pointer<
28
        NativeFunction<
29
            Int32 Function(
30
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
31
    _parseMethodVisitorPtr;
32
Pointer<
33
        NativeFunction<
34
            Int32 Function(
35
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
36
    _findCategoryInterfaceVisitorPtr;
37

38
class _ParsedObjCInterface {
39
  ObjCInterface interface;
40
  _ParsedObjCInterface(this.interface);
2✔
41
}
42

43
class _ParsedObjCMethod {
44
  ObjCMethod method;
45
  bool hasError = false;
46
  _ParsedObjCMethod(this.method);
2✔
47
}
48

49
final _interfaceStack = Stack<_ParsedObjCInterface>();
6✔
50
final _methodStack = Stack<_ParsedObjCMethod>();
6✔
51

52
Type? parseObjCInterfaceDeclaration(
2✔
53
  clang_types.CXCursor cursor, {
54
  /// Option to ignore declaration filter (Useful in case of extracting
55
  /// declarations when they are passed/returned by an included function.)
56
  bool ignoreFilter = false,
57
}) {
58
  final itfUsr = cursor.usr();
2✔
59
  final itfName = cursor.spelling();
2✔
60
  if (!ignoreFilter && !shouldIncludeObjCInterface(itfUsr, itfName)) {
2✔
61
    return null;
62
  }
63

64
  final t = cursor.type();
2✔
65
  final name = t.spelling();
2✔
66

67
  _logger.fine('++++ Adding ObjC interface: '
6✔
68
      'Name: $name, ${cursor.completeStringRepr()}');
2✔
69

70
  return ObjCInterface(
2✔
71
    usr: itfUsr,
72
    originalName: name,
73
    name: config.objcInterfaces.renameUsingConfig(name),
6✔
74
    lookupName: config.objcModulePrefixer.applyPrefix(name),
6✔
75
    dartDoc: getCursorDocComment(cursor),
2✔
76
    builtInFunctions: objCBuiltInFunctions,
2✔
77
  );
78
}
79

80
void fillObjCInterfaceMethodsIfNeeded(
2✔
81
    ObjCInterface itf, clang_types.CXCursor cursor) {
82
  if (_isClassDeclaration(cursor)) {
2✔
83
    // @class declarations are ObjC's way of forward declaring classes. In that
84
    // case there's nothing to fill yet.
85
    return;
86
  }
87

88
  if (itf.filled) return;
2✔
89
  itf.filled = true; // Break cycles.
2✔
90

91
  _logger.fine('++++ Filling ObjC interface: '
6✔
92
      'Name: ${itf.originalName}, ${cursor.completeStringRepr()}');
4✔
93

94
  _interfaceStack.push(_ParsedObjCInterface(itf));
6✔
95
  clang.clang_visitChildren(
4✔
96
      cursor,
97
      _parseInterfaceVisitorPtr ??= Pointer.fromFunction(
98
          _parseInterfaceVisitor, exceptional_visitor_return),
99
      nullptr);
2✔
100
  _interfaceStack.pop();
4✔
101

102
  _logger.fine('++++ Finished ObjC interface: '
6✔
103
      'Name: ${itf.originalName}, ${cursor.completeStringRepr()}');
4✔
104
}
105

106
bool _isClassDeclarationResult = false;
107
bool _isClassDeclaration(clang_types.CXCursor cursor) {
2✔
108
  // It's a class declaration if it has no children other than ObjCClassRef.
109
  _isClassDeclarationResult = true;
110
  clang.clang_visitChildren(
4✔
111
      cursor,
112
      _isClassDeclarationVisitorPtr ??= Pointer.fromFunction(
113
          _isClassDeclarationVisitor, exceptional_visitor_return),
114
      nullptr);
2✔
115
  return _isClassDeclarationResult;
116
}
117

118
int _isClassDeclarationVisitor(clang_types.CXCursor cursor,
2✔
119
    clang_types.CXCursor parent, Pointer<Void> clientData) {
120
  if (cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassRef) {
4✔
121
    return clang_types.CXChildVisitResult.CXChildVisit_Continue;
122
  }
123
  _isClassDeclarationResult = false;
124
  return clang_types.CXChildVisitResult.CXChildVisit_Break;
125
}
126

127
int _parseInterfaceVisitor(clang_types.CXCursor cursor,
2✔
128
    clang_types.CXCursor parent, Pointer<Void> clientData) {
129
  switch (cursor.kind) {
2✔
130
    case clang_types.CXCursorKind.CXCursor_ObjCSuperClassRef:
2✔
131
      _parseSuperType(cursor);
2✔
132
      break;
133
    case clang_types.CXCursorKind.CXCursor_ObjCPropertyDecl:
2✔
134
      _parseProperty(cursor);
2✔
135
      break;
136
    case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl:
2✔
137
    case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl:
2✔
138
      _parseMethod(cursor);
2✔
139
      break;
140
  }
141
  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
142
}
143

144
void _parseSuperType(clang_types.CXCursor cursor) {
2✔
145
  final superType = cursor.type().toCodeGenType();
4✔
146
  _logger.fine('       > Super type: '
6✔
147
      '$superType ${cursor.completeStringRepr()}');
2✔
148
  final itf = _interfaceStack.top.interface;
6✔
149
  if (superType is ObjCInterface) {
2✔
150
    itf.superType = superType;
2✔
151
  } else {
152
    _logger.severe(
×
153
        'Super type of $itf is $superType, which is not a valid interface.');
×
154
  }
155
}
156

157
void _parseProperty(clang_types.CXCursor cursor) {
2✔
158
  final itf = _interfaceStack.top.interface;
6✔
159
  final fieldName = cursor.spelling();
2✔
160
  final fieldType = cursor.type().toCodeGenType();
4✔
161

162
  if (fieldType.isIncompleteCompound) {
2✔
163
    _logger.warning('Property "$fieldName" in instance "${itf.originalName}" '
8✔
164
        'has incomplete type: $fieldType.');
165
    return;
166
  }
167

168
  final dartDoc = getCursorDocComment(cursor);
2✔
169

170
  final propertyAttributes =
171
      clang.clang_Cursor_getObjCPropertyAttributes(cursor, 0);
4✔
172
  final isClass = propertyAttributes &
2✔
173
          clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_class >
2✔
174
      0;
175
  final isReadOnly = propertyAttributes &
2✔
176
          clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly >
2✔
177
      0;
178
  // TODO(#334): Use the nullable attribute to decide this.
179
  final isNullable =
180
      cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
6✔
181

182
  final property = ObjCProperty(fieldName);
2✔
183

184
  _logger.fine('       > Property: '
6✔
185
      '$fieldType $fieldName ${cursor.completeStringRepr()}');
2✔
186

187
  final getterName =
188
      clang.clang_Cursor_getObjCPropertyGetterName(cursor).toStringAndDispose();
6✔
189
  final getter = ObjCMethod(
2✔
190
    originalName: getterName,
191
    property: property,
192
    dartDoc: dartDoc,
193
    kind: ObjCMethodKind.propertyGetter,
194
    isClass: isClass,
195
    returnType: fieldType,
196
    isNullableReturn: isNullable,
197
  );
198
  itf.addMethod(getter);
2✔
199

200
  if (!isReadOnly) {
201
    final setterName = clang
2✔
202
        .clang_Cursor_getObjCPropertySetterName(cursor)
2✔
203
        .toStringAndDispose();
2✔
204
    final setter = ObjCMethod(
2✔
205
        originalName: setterName,
206
        property: property,
207
        dartDoc: dartDoc,
208
        kind: ObjCMethodKind.propertySetter,
209
        isClass: isClass,
210
        returnType: NativeType(SupportedNativeType.Void));
2✔
211
    setter.params
2✔
212
        .add(ObjCMethodParam(fieldType, 'value', isNullable: isNullable));
4✔
213
    itf.addMethod(setter);
2✔
214
  }
215
}
216

217
void _parseMethod(clang_types.CXCursor cursor) {
2✔
218
  final methodName = cursor.spelling();
2✔
219
  final isClassMethod =
220
      cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
4✔
221
  final returnType = clang.clang_getCursorResultType(cursor).toCodeGenType();
6✔
222
  if (returnType.isIncompleteCompound) {
2✔
223
    _logger.warning('Method "$methodName" in instance '
6✔
224
        '"${_interfaceStack.top.interface.originalName}" has incomplete '
8✔
225
        'return type: $returnType.');
226
    return;
227
  }
228
  final method = ObjCMethod(
2✔
229
    originalName: methodName,
230
    dartDoc: getCursorDocComment(cursor),
2✔
231
    kind: ObjCMethodKind.method,
232
    isClass: isClassMethod,
233
    returnType: returnType,
234
  );
235
  final parsed = _ParsedObjCMethod(method);
2✔
236
  _logger.fine('       > ${isClassMethod ? 'Class' : 'Instance'} method: '
6✔
237
      '${method.originalName} ${cursor.completeStringRepr()}');
4✔
238
  _methodStack.push(parsed);
4✔
239
  clang.clang_visitChildren(
4✔
240
      cursor,
241
      _parseMethodVisitorPtr ??=
242
          Pointer.fromFunction(_parseMethodVisitor, exceptional_visitor_return),
243
      nullptr);
2✔
244
  _methodStack.pop();
4✔
245
  if (parsed.hasError) {
2✔
246
    // Discard it.
247
    return;
248
  }
249
  _interfaceStack.top.interface.addMethod(method);
8✔
250
}
251

252
int _parseMethodVisitor(clang_types.CXCursor cursor,
2✔
253
    clang_types.CXCursor parent, Pointer<Void> clientData) {
254
  switch (cursor.kind) {
2✔
255
    case clang_types.CXCursorKind.CXCursor_ParmDecl:
2✔
256
      _parseMethodParam(cursor);
2✔
257
      break;
258
    case clang_types.CXCursorKind.CXCursor_NSReturnsRetained:
2✔
259
      _markMethodReturnsRetained(cursor);
2✔
260
      break;
261
    default:
262
  }
263
  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
264
}
265

266
void _parseMethodParam(clang_types.CXCursor cursor) {
2✔
267
  /*
268
  TODO(#334): Change this to use:
269
  
270
  clang.clang_Type_getNullability(cursor.type()) ==
271
      clang_types.CXTypeNullabilityKind.CXTypeNullability_Nullable;
272

273
  NOTE: This will only work with the
274

275
    clang_types
276
      .CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes
277

278
  option set.
279
  */
280
  final parsed = _methodStack.top;
4✔
281
  final isNullable =
282
      cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
6✔
283
  final name = cursor.spelling();
2✔
284
  final type = cursor.type().toCodeGenType();
4✔
285
  if (type.isIncompleteCompound) {
2✔
286
    parsed.hasError = true;
×
287
    _logger.warning('Method "${parsed.method.originalName}" in instance '
×
288
        '"${_interfaceStack.top.interface.originalName}" has incomplete '
×
289
        'parameter type: $type.');
290
    return;
291
  }
292
  _logger.fine(
4✔
293
      '           >> Parameter: $type $name ${cursor.completeStringRepr()}');
4✔
294
  parsed.method.params.add(ObjCMethodParam(type, name, isNullable: isNullable));
8✔
295
}
296

297
void _markMethodReturnsRetained(clang_types.CXCursor cursor) {
2✔
298
  _methodStack.top.method.returnsRetained = true;
8✔
299
}
300

301
BindingType? parseObjCCategoryDeclaration(clang_types.CXCursor cursor) {
2✔
302
  // Categories add methods to an existing interface, so first we run a visitor
303
  // to find the interface, then we fully parse that interface, then we run the
304
  // _parseInterfaceVisitor over the category to add its methods etc. Reusing
305
  // the interface visitor relies on the fact that the structure of the category
306
  // AST looks exactly the same as the interface AST, and that the category's
307
  // interface is a different kind of node to the interface's super type (so is
308
  // ignored by _parseInterfaceVisitor).
309
  final name = cursor.spelling();
2✔
310
  _logger.fine('++++ Adding ObjC category: '
6✔
311
      'Name: $name, ${cursor.completeStringRepr()}');
2✔
312

313
  _findCategoryInterfaceVisitorResult = null;
314
  clang.clang_visitChildren(
4✔
315
      cursor,
316
      _findCategoryInterfaceVisitorPtr ??= Pointer.fromFunction(
317
          _findCategoryInterfaceVisitor, exceptional_visitor_return),
318
      nullptr);
2✔
319
  final itfCursor = _findCategoryInterfaceVisitorResult;
320
  if (itfCursor == null) {
321
    _logger.severe('Category $name has no interface.');
×
322
    return null;
323
  }
324

325
  // TODO(#347): Currently any interface with a category bypasses the filters.
326
  final itf = itfCursor.type().toCodeGenType();
4✔
327
  if (itf is! ObjCInterface) {
2✔
328
    _logger.severe(
×
329
        'Interface of category $name is $itf, which is not a valid interface.');
×
330
    return null;
331
  }
332

333
  _interfaceStack.push(_ParsedObjCInterface(itf));
6✔
334
  clang.clang_visitChildren(
4✔
335
      cursor,
336
      _parseInterfaceVisitorPtr ??= Pointer.fromFunction(
337
          _parseInterfaceVisitor, exceptional_visitor_return),
338
      nullptr);
2✔
339
  _interfaceStack.pop();
4✔
340

341
  _logger.fine('++++ Finished ObjC category: '
6✔
342
      'Name: $name, ${cursor.completeStringRepr()}');
2✔
343

344
  return itf;
345
}
346

347
clang_types.CXCursor? _findCategoryInterfaceVisitorResult;
348
int _findCategoryInterfaceVisitor(clang_types.CXCursor cursor,
2✔
349
    clang_types.CXCursor parent, Pointer<Void> clientData) {
350
  if (cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassRef) {
4✔
351
    _findCategoryInterfaceVisitorResult = cursor;
352
    return clang_types.CXChildVisitResult.CXChildVisit_Break;
353
  }
354
  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
355
}
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

© 2026 Coveralls, Inc