• 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.61
/lib/src/header_parser/type_extractor/extractor.dart
1
// Copyright (c) 2020, 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
/// Extracts code_gen Type from type.
6
import 'package:ffigen/src/code_generator.dart';
7
import 'package:ffigen/src/header_parser/sub_parsers/function_type_param_parser.dart';
8
import 'package:ffigen/src/header_parser/sub_parsers/typedefdecl_parser.dart';
9
import 'package:ffigen/src/strings.dart' as strings;
10
import 'package:logging/logging.dart';
11

12
import '../../config_provider/config_types.dart';
13
import '../clang_bindings/clang_bindings.dart' as clang_types;
14
import '../data.dart';
15
import '../sub_parsers/compounddecl_parser.dart';
16
import '../sub_parsers/enumdecl_parser.dart';
17
import '../sub_parsers/objc_block_parser.dart';
18
import '../sub_parsers/objcinterfacedecl_parser.dart';
19
import '../type_extractor/cxtypekindmap.dart';
20
import '../utils.dart';
21

22
final _logger = Logger('ffigen.header_parser.extractor');
93✔
23
const _padding = '  ';
24

25
const maxRecursionDepth = 5;
26

27
/// Converts cxtype to a typestring code_generator can accept.
28
Type getCodeGenType(
31✔
29
  clang_types.CXType cxtype, {
30
  /// Option to ignore declaration filter (Useful in case of extracting
31
  /// declarations when they are passed/returned by an included function.)
32
  bool ignoreFilter = true,
33

34
  /// Passed on if a value was marked as a pointer before this one.
35
  bool pointerReference = false,
36

37
  /// Cursor of the declaration, currently this is useful only to extract
38
  /// parameter names in function types.
39
  clang_types.CXCursor? originalCursor,
40
}) {
41
  _logger.fine('${_padding}getCodeGenType ${cxtype.completeStringRepr()}');
124✔
42

43
  // Special case: Elaborated types just refer to another type.
44
  if (cxtype.kind == clang_types.CXTypeKind.CXType_Elaborated) {
62✔
45
    return getCodeGenType(clang.clang_Type_getNamedType(cxtype),
54✔
46
        ignoreFilter: ignoreFilter, pointerReference: pointerReference);
47
  }
48

49
  // These basic Objective C types skip the cache, and are conditional on the
50
  // language flag.
51
  if (config.language == Language.objc) {
93✔
52
    switch (cxtype.kind) {
2✔
53
      case clang_types.CXTypeKind.CXType_ObjCObjectPointer:
2✔
54
        final pt = clang.clang_getPointeeType(cxtype);
4✔
55
        final s = getCodeGenType(pt,
2✔
56
            ignoreFilter: ignoreFilter, pointerReference: true);
57
        if (s is ObjCInterface) {
2✔
58
          return s;
59
        }
60
        return PointerType(objCObjectType);
4✔
61
      case clang_types.CXTypeKind.CXType_ObjCId:
2✔
62
      case clang_types.CXTypeKind.CXType_ObjCTypeParam:
2✔
63
      case clang_types.CXTypeKind.CXType_ObjCClass:
2✔
64
        return PointerType(objCObjectType);
4✔
65
      case clang_types.CXTypeKind.CXType_ObjCSel:
2✔
66
        return PointerType(objCSelType);
4✔
67
      case clang_types.CXTypeKind.CXType_BlockPointer:
2✔
68
        return _getOrCreateBlockType(cxtype);
2✔
69
    }
70
  }
71

72
  // If the type has a declaration cursor, then use the BindingsIndex to break
73
  // any potential cycles, and dedupe the Type.
74
  final cursor = clang.clang_getTypeDeclaration(cxtype);
62✔
75
  if (cursor.kind != clang_types.CXCursorKind.CXCursor_NoDeclFound) {
62✔
76
    final usr = cursor.usr();
28✔
77
    var type = bindingsIndex.getSeenType(usr);
56✔
78
    if (type == null) {
79
      final result =
80
          _createTypeFromCursor(cxtype, cursor, ignoreFilter, pointerReference);
28✔
81
      type = result.type;
28✔
82
      if (type == null) {
83
        return UnimplementedType('${cxtype.kindSpelling()} not implemented');
24✔
84
      }
85
      if (result.addToCache) {
28✔
86
        bindingsIndex.addTypeToSeen(usr, type);
54✔
87
      }
88
    }
89
    _fillFromCursorIfNeeded(type, cursor, ignoreFilter, pointerReference);
28✔
90
    return type;
91
  }
92

93
  // If the type doesn't have a declaration cursor, then it's a basic type such
94
  // as int, or a simple derived type like a pointer, so doesn't need to be
95
  // cached.
96
  switch (cxtype.kind) {
30✔
97
    case clang_types.CXTypeKind.CXType_Pointer:
30✔
98
      final pt = clang.clang_getPointeeType(cxtype);
46✔
99
      final s = getCodeGenType(
23✔
100
        pt,
101
        pointerReference: true,
102
        originalCursor: originalCursor,
103
      );
104

105
      // Replace Pointer<_Dart_Handle> with Handle.
106
      if (config.useDartHandle &&
46✔
107
          s is Compound &&
23✔
108
          s.compoundType == CompoundType.struct &&
28✔
109
          s.usr == strings.dartHandleUsr) {
26✔
110
        return HandleType();
1✔
111
      }
112
      return PointerType(s);
23✔
113
    case clang_types.CXTypeKind.CXType_FunctionProto:
30✔
114
      // Primarily used for function pointers.
115
      return _extractFromFunctionProto(cxtype, cursor: originalCursor);
10✔
116
    case clang_types.CXTypeKind.CXType_FunctionNoProto:
30✔
117
      // Primarily used for function types with zero arguments.
118
      return _extractFromFunctionProto(cxtype, cursor: originalCursor);
3✔
119
    case clang_types.CXTypeKind
120
        .CXType_ConstantArray: // Primarily used for constant array in struct members.
30✔
121
      return ConstantArray(
6✔
122
        clang.clang_getNumElements(cxtype),
12✔
123
        clang.clang_getArrayElementType(cxtype).toCodeGenType(),
18✔
124
      );
125
    case clang_types.CXTypeKind
126
        .CXType_IncompleteArray: // Primarily used for incomplete array in function parameters.
30✔
127
      return IncompleteArray(
2✔
128
        clang.clang_getArrayElementType(cxtype).toCodeGenType(),
6✔
129
      );
130
    case clang_types.CXTypeKind.CXType_Bool:
30✔
131
      return BooleanType();
3✔
132
    default:
133
      var typeSpellKey =
134
          clang.clang_getTypeSpelling(cxtype).toStringAndDispose();
90✔
135
      if (typeSpellKey.startsWith('const ')) {
30✔
136
        typeSpellKey = typeSpellKey.replaceFirst('const ', '');
5✔
137
      }
138
      if (config.nativeTypeMappings.containsKey(typeSpellKey)) {
90✔
139
        _logger.fine('  Type $typeSpellKey mapped from type-map.');
×
140
        return config.nativeTypeMappings[typeSpellKey]!;
×
141
      } else if (cxTypeKindToImportedTypes.containsKey(typeSpellKey)) {
60✔
142
        return cxTypeKindToImportedTypes[typeSpellKey]!;
58✔
143
      } else {
144
        _logger.fine('typedeclarationCursorVisitor: getCodeGenType: Type Not '
21✔
145
            'Implemented, ${cxtype.completeStringRepr()}');
7✔
146
        return UnimplementedType('${cxtype.kindSpelling()} not implemented');
21✔
147
      }
148
  }
149
}
150

151
Type _getOrCreateBlockType(clang_types.CXType cxtype) {
2✔
152
  final block = parseObjCBlock(cxtype);
2✔
153
  final key = block.usr;
2✔
154
  final oldBlock = bindingsIndex.getSeenObjCBlock(key);
4✔
155
  if (oldBlock != null) {
156
    return oldBlock;
157
  }
158
  bindingsIndex.addObjCBlockToSeen(key, block);
4✔
159
  return block;
160
}
161

162
class _CreateTypeFromCursorResult {
163
  final Type? type;
164

165
  // Flag that controls whether the type is added to the cache. It should not
166
  // be added to the cache if it's just a fallback implementation, such as the
167
  // int that is returned when an enum is excluded by the config. Later we might
168
  // need to build the full enum type (eg if it's part of an included struct),
169
  // and if we put the fallback int in the cache then the full enum will never
170
  // be created.
171
  final bool addToCache;
172

173
  _CreateTypeFromCursorResult(this.type, {this.addToCache = true});
28✔
174
}
175

176
_CreateTypeFromCursorResult _createTypeFromCursor(clang_types.CXType cxtype,
28✔
177
    clang_types.CXCursor cursor, bool ignoreFilter, bool pointerReference) {
178
  switch (cxtype.kind) {
28✔
179
    case clang_types.CXTypeKind.CXType_Typedef:
28✔
180
      final spelling = clang.clang_getTypedefName(cxtype).toStringAndDispose();
54✔
181
      if (config.language == Language.objc && spelling == strings.objcBOOL) {
56✔
182
        // Objective C's BOOL type can be either bool or signed char, depending
183
        // on the platform. We want to present a consistent API to the user, and
184
        // those two types are ABI compatible, so just return bool regardless.
185
        return _CreateTypeFromCursorResult(BooleanType());
4✔
186
      }
187
      final usr = cursor.usr();
18✔
188
      if (config.typedefTypeMappings.containsKey(spelling)) {
54✔
189
        _logger.fine('  Type $spelling mapped from type-map');
9✔
190
        return _CreateTypeFromCursorResult(
3✔
191
            config.typedefTypeMappings[spelling]!);
9✔
192
      }
193
      if (config.usrTypeMappings.containsKey(usr)) {
54✔
194
        _logger.fine('  Type $spelling mapped from usr');
3✔
195
        return _CreateTypeFromCursorResult(config.usrTypeMappings[usr]!);
4✔
196
      }
197
      // Get name from supported typedef name if config allows.
198
      if (config.useSupportedTypedefs) {
36✔
199
        if (suportedTypedefToSuportedNativeType.containsKey(spelling)) {
36✔
200
          _logger.fine('  Type Mapped from supported typedef');
10✔
201
          return _CreateTypeFromCursorResult(
5✔
202
              NativeType(suportedTypedefToSuportedNativeType[spelling]!));
15✔
203
        } else if (supportedTypedefToImportedType.containsKey(spelling)) {
34✔
204
          _logger.fine('  Type Mapped from supported typedef');
8✔
205
          return _CreateTypeFromCursorResult(
4✔
206
              supportedTypedefToImportedType[spelling]!);
8✔
207
        }
208
      }
209

210
      final typealias =
211
          parseTypedefDeclaration(cursor, pointerReference: pointerReference);
16✔
212

213
      if (typealias != null) {
214
        return _CreateTypeFromCursorResult(typealias);
15✔
215
      } else {
216
        // Use underlying type if typealias couldn't be created or if the user
217
        // excluded this typedef.
218
        final ct = clang.clang_getTypedefDeclUnderlyingType(cursor);
18✔
219
        return _CreateTypeFromCursorResult(
9✔
220
            getCodeGenType(ct, pointerReference: pointerReference),
9✔
221
            addToCache: false);
222
      }
223
    case clang_types.CXTypeKind.CXType_Record:
26✔
224
      return _CreateTypeFromCursorResult(
25✔
225
          _extractfromRecord(cxtype, cursor, ignoreFilter, pointerReference));
25✔
226
    case clang_types.CXTypeKind.CXType_Enum:
11✔
227
      final enumClass = parseEnumDeclaration(
11✔
228
        cursor,
229
        ignoreFilter: ignoreFilter,
230
      );
231
      if (enumClass == null) {
232
        // Handle anonymous enum declarations within another declaration.
233
        return _CreateTypeFromCursorResult(EnumClass.nativeType,
16✔
234
            addToCache: false);
235
      } else {
236
        return _CreateTypeFromCursorResult(enumClass);
10✔
237
      }
238
    case clang_types.CXTypeKind.CXType_ObjCInterface:
2✔
239
      return _CreateTypeFromCursorResult(
2✔
240
          parseObjCInterfaceDeclaration(cursor, ignoreFilter: ignoreFilter));
2✔
241
    default:
242
      return _CreateTypeFromCursorResult(
2✔
243
          UnimplementedType('Unknown type: ${cxtype.completeStringRepr()}'),
6✔
244
          addToCache: false);
245
  }
246
}
247

248
void _fillFromCursorIfNeeded(Type? type, clang_types.CXCursor cursor,
28✔
249
    bool ignoreFilter, bool pointerReference) {
250
  if (type == null) return;
251
  if (type is Compound) {
28✔
252
    fillCompoundMembersIfNeeded(type, cursor,
25✔
253
        ignoreFilter: ignoreFilter, pointerReference: pointerReference);
254
  } else if (type is ObjCInterface) {
21✔
255
    fillObjCInterfaceMethodsIfNeeded(type, cursor);
2✔
256
  }
257
}
258

259
Type? _extractfromRecord(clang_types.CXType cxtype, clang_types.CXCursor cursor,
25✔
260
    bool ignoreFilter, bool pointerReference) {
261
  _logger.fine('${_padding}_extractfromRecord: ${cursor.completeStringRepr()}');
100✔
262

263
  final cursorKind = clang.clang_getCursorKind(cursor);
50✔
264
  if (cursorKind == clang_types.CXCursorKind.CXCursor_StructDecl ||
25✔
265
      cursorKind == clang_types.CXCursorKind.CXCursor_UnionDecl) {
8✔
266
    final declSpelling = cursor.spelling();
25✔
267
    final declUsr = cursor.usr();
25✔
268

269
    // Set includer functions according to compoundType.
270
    final CompoundType compoundType;
271
    final Map<String, ImportedType> compoundTypeMappings;
272

273
    switch (cursorKind) {
274
      case clang_types.CXCursorKind.CXCursor_StructDecl:
25✔
275
        compoundType = CompoundType.struct;
276
        compoundTypeMappings = config.structTypeMappings;
48✔
277
        break;
278
      case clang_types.CXCursorKind.CXCursor_UnionDecl:
8✔
279
        compoundType = CompoundType.union;
280
        compoundTypeMappings = config.unionTypeMappings;
16✔
281
        break;
282
      default:
283
        throw Exception('Unhandled compound type cursorkind.');
×
284
    }
285

286
    // Also add a struct binding, if its unseen.
287
    if (compoundTypeMappings.containsKey(declSpelling)) {
25✔
288
      _logger.fine('  Type Mapped from type-map');
2✔
289
      return compoundTypeMappings[declSpelling]!;
1✔
290
    } else if (config.usrTypeMappings.containsKey(declUsr)) {
75✔
291
      _logger.fine('  Type Mapped from usr');
2✔
292
      return config.usrTypeMappings[declUsr]!;
3✔
293
    } else {
294
      final struct = parseCompoundDeclaration(
25✔
295
        cursor,
296
        compoundType,
297
        ignoreFilter: ignoreFilter,
298
        pointerReference: pointerReference,
299
      );
300
      return struct;
301
    }
302
  }
303
  _logger.fine('typedeclarationCursorVisitor: _extractfromRecord: '
×
304
      'Not Implemented, ${cursor.completeStringRepr()}');
×
305
  return UnimplementedType('${cxtype.kindSpelling()} not implemented');
×
306
}
307

308
// Used for function pointer arguments.
309
Type _extractFromFunctionProto(clang_types.CXType cxtype,
10✔
310
    {clang_types.CXCursor? cursor}) {
311
  final parameters = <Parameter>[];
10✔
312
  final totalArgs = clang.clang_getNumArgTypes(cxtype);
20✔
313
  for (var i = 0; i < totalArgs; i++) {
20✔
314
    final t = clang.clang_getArgType(cxtype, i);
20✔
315
    final pt = t.toCodeGenType();
10✔
316

317
    if (pt.isIncompleteCompound) {
10✔
318
      return UnimplementedType(
×
319
          'Incomplete Struct by value in function parameter.');
320
    } else if (pt.baseType is UnimplementedType) {
20✔
321
      return UnimplementedType('Function parameter has an unsupported type.');
2✔
322
    }
323

324
    parameters.add(
10✔
325
      Parameter(name: '', type: pt),
10✔
326
    );
327
  }
328

329
  final functionType = FunctionType(
10✔
330
    parameters: parameters,
331
    returnType: clang.clang_getResultType(cxtype).toCodeGenType(),
30✔
332
  );
333
  _parseAndMergeParamNames(functionType, cursor, maxRecursionDepth);
10✔
334
  return NativeFunc(functionType);
10✔
335
}
336

337
void _parseAndMergeParamNames(
10✔
338
  FunctionType functionType,
339
  clang_types.CXCursor? cursor,
340
  int recursionDepth,
341
) {
342
  if (cursor == null) {
343
    return;
344
  }
345
  if (recursionDepth == 0) {
8✔
346
    final cursorRepr = cursor.completeStringRepr();
×
347
    _logger.warning('Recursion depth exceeded when merging function parameters.'
×
348
        ' Last cursor encountered was $cursorRepr');
349
    return;
350
  }
351

352
  final paramsInfo = parseFunctionPointerParamNames(cursor);
8✔
353
  functionType.addParameterNames(paramsInfo.paramNames);
16✔
354

355
  for (final param in functionType.parameters) {
16✔
356
    final paramRealType = param.type.typealiasType;
16✔
357
    final paramBaseType = paramRealType.baseType.typealiasType;
16✔
358
    if (paramBaseType is NativeFunc && param.name.isNotEmpty) {
16✔
359
      final paramFunctionType = paramBaseType.type;
4✔
360
      final paramCursor = paramsInfo.params[param.name];
12✔
361
      _parseAndMergeParamNames(
4✔
362
        paramFunctionType,
363
        paramCursor,
364
        recursionDepth - 1,
4✔
365
      );
366
    }
367
  }
368
}
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