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

dart-lang / ffigen / 4153774002

pending completion
4153774002

push

github

GitHub
Add support nested anonymous union/struct (#511)

42 of 42 new or added lines in 3 files covered. (100.0%)

3163 of 3441 relevant lines covered (91.92%)

24.72 hits per line

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

97.04
/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
1
// Copyright (c) 2021, 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/config_provider/config_types.dart';
9
import 'package:logging/logging.dart';
10

11
import '../../strings.dart' as strings;
12
import '../clang_bindings/clang_bindings.dart' as clang_types;
13
import '../data.dart';
14
import '../includer.dart';
15
import '../utils.dart';
16

17
final _logger = Logger('ffigen.header_parser.compounddecl_parser');
72✔
18

19
Pointer<
20
        NativeFunction<
21
            Int32 Function(
22
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
23
    _compoundMembersVisitorPtr;
24

25
/// Holds temporary information regarding [compound] while parsing.
26
class _ParsedCompound {
27
  Compound compound;
28
  bool unimplementedMemberType = false;
29
  bool flexibleArrayMember = false;
30
  bool bitFieldMember = false;
31
  bool dartHandleMember = false;
32
  bool incompleteCompoundMember = false;
33

34
  _ParsedCompound(this.compound);
24✔
35

36
  bool get isIncomplete =>
24✔
37
      unimplementedMemberType ||
24✔
38
      flexibleArrayMember ||
24✔
39
      bitFieldMember ||
24✔
40
      (dartHandleMember && config.useDartHandle) ||
26✔
41
      incompleteCompoundMember ||
24✔
42
      alignment == clang_types.CXTypeLayoutError.CXTypeLayoutError_Incomplete;
48✔
43

44
  // A struct without any attribute is definitely not packed. #pragma pack(...)
45
  // also adds an attribute, but it's unexposed and cannot be travesed.
46
  bool hasAttr = false;
47

48
  // A struct which as a __packed__ attribute is definitely packed.
49
  bool hasPackedAttr = false;
50

51
  // Stores the maximum alignment from all the children.
52
  int maxChildAlignment = 0;
53

54
  // Alignment of this struct.
55
  int alignment = 0;
56

57
  bool get _isPacked {
23✔
58
    if (!hasAttr || isIncomplete) return false;
27✔
59
    if (hasPackedAttr) return true;
4✔
60

61
    return maxChildAlignment > alignment;
12✔
62
  }
63

64
  /// Returns pack value of a struct depending on config, returns null for no
65
  /// packing.
66
  int? get packValue {
24✔
67
    if (compound.isStruct && _isPacked && !isIncomplete) {
75✔
68
      if (strings.packingValuesMap.containsKey(alignment)) {
8✔
69
        return alignment;
4✔
70
      } else {
71
        _logger.warning(
×
72
            'Unsupported pack value "$alignment" for Struct "${compound.name}".');
×
73
        return null;
74
      }
75
    } else {
76
      return null;
77
    }
78
  }
79
}
80

81
final _stack = Stack<_ParsedCompound>();
72✔
82

83
/// Parses a compound declaration.
84
Compound? parseCompoundDeclaration(
24✔
85
  clang_types.CXCursor cursor,
86
  CompoundType compoundType, {
87
  /// Option to ignore declaration filter (Useful in case of extracting
88
  /// declarations when they are passed/returned by an included function.)
89
  bool ignoreFilter = false,
90

91
  /// To track if the declaration was used by reference(i.e T*). (Used to only
92
  /// generate these as opaque if `dependency-only` was set to opaque).
93
  bool pointerReference = false,
94
}) {
95
  // Set includer functions according to compoundType.
96
  final bool Function(String, String) shouldIncludeDecl;
97
  final Declaration configDecl;
98
  final String className = _compoundTypeDebugName(compoundType);
24✔
99
  switch (compoundType) {
100
    case CompoundType.struct:
24✔
101
      shouldIncludeDecl = shouldIncludeStruct;
102
      configDecl = config.structDecl;
46✔
103
      break;
104
    case CompoundType.union:
8✔
105
      shouldIncludeDecl = shouldIncludeUnion;
106
      configDecl = config.unionDecl;
16✔
107
      break;
108
  }
109

110
  // Parse the cursor definition instead, if this is a forward declaration.
111
  cursor = cursorIndex.getDefinition(cursor);
48✔
112
  final declUsr = cursor.usr();
24✔
113
  final String declName;
114

115
  // Only set name using USR if the type is not Anonymous (A struct is anonymous
116
  // if it has no name, is not inside any typedef and declared inline inside
117
  // another declaration).
118
  if (clang.clang_Cursor_isAnonymous(cursor) == 0) {
72✔
119
    // This gives the significant name, i.e name of the struct if defined or
120
    // name of the first typedef declaration that refers to it.
121
    declName = declUsr.split('@').last;
48✔
122
  } else {
123
    // Empty names are treated as inline declarations.
124
    declName = '';
125
  }
126

127
  if (declName.isEmpty) {
24✔
128
    if (ignoreFilter) {
129
      // This declaration is defined inside some other declaration and hence
130
      // must be generated.
131
      return Compound.fromType(
1✔
132
        type: compoundType,
133
        name: incrementalNamer.name('Unnamed$className'),
3✔
134
        usr: declUsr,
135
        dartDoc: getCursorDocComment(cursor),
1✔
136
      );
137
    } else {
138
      _logger.finest('unnamed $className declaration');
3✔
139
    }
140
  } else if (ignoreFilter || shouldIncludeDecl(declUsr, declName)) {
24✔
141
    _logger.fine(
48✔
142
        '++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}');
48✔
143
    return Compound.fromType(
24✔
144
      type: compoundType,
145
      usr: declUsr,
146
      originalName: declName,
147
      name: configDecl.renameUsingConfig(declName),
24✔
148
      dartDoc: getCursorDocComment(cursor),
24✔
149
    );
150
  }
151
  return null;
152
}
153

154
void fillCompoundMembersIfNeeded(
24✔
155
  Compound compound,
156
  clang_types.CXCursor cursor, {
157
  /// Option to ignore declaration filter (Useful in case of extracting
158
  /// declarations when they are passed/returned by an included function.)
159
  bool ignoreFilter = false,
160

161
  /// To track if the declaration was used by reference(i.e T*). (Used to only
162
  /// generate these as opaque if `dependency-only` was set to opaque).
163
  bool pointerReference = false,
164
}) {
165
  cursor = cursorIndex.getDefinition(cursor);
48✔
166
  final compoundType = compound.compoundType;
24✔
167

168
  // Skip dependencies if already seen OR user has specified `dependency-only`
169
  // as opaque AND this is a pointer reference AND the declaration was not
170
  // included according to config (ignoreFilter).
171
  final skipDependencies = compound.parsedDependencies ||
24✔
172
      (pointerReference &&
173
          ignoreFilter &&
174
          ((compoundType == CompoundType.struct &&
8✔
175
                  config.structDependencies == CompoundDependencies.opaque) ||
24✔
176
              (compoundType == CompoundType.union &&
8✔
177
                  config.unionDependencies == CompoundDependencies.opaque)));
3✔
178
  if (skipDependencies) return;
179

180
  final parsed = _ParsedCompound(compound);
24✔
181
  final String className = _compoundTypeDebugName(compoundType);
24✔
182
  parsed.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
96✔
183
  parsed.alignment = cursor.type().alignment();
72✔
184
  compound.parsedDependencies = true; // Break cycles.
24✔
185

186
  _stack.push(parsed);
48✔
187
  final resultCode = clang.clang_visitChildren(
48✔
188
    cursor,
189
    _compoundMembersVisitorPtr ??= Pointer.fromFunction(
190
        _compoundMembersVisitor, exceptional_visitor_return),
191
    nullptr,
24✔
192
  );
193
  _stack.pop();
48✔
194

195
  _logger.finest(
48✔
196
      'Opaque: ${parsed.isIncomplete}, HasAttr: ${parsed.hasAttr}, AlignValue: ${parsed.alignment}, MaxChildAlignValue: ${parsed.maxChildAlignment}, PackValue: ${parsed.packValue}.');
144✔
197
  compound.pack = parsed.packValue;
48✔
198

199
  visitChildrenResultChecker(resultCode);
24✔
200

201
  if (parsed.unimplementedMemberType) {
24✔
202
    _logger.fine(
4✔
203
        '---- Removed $className members, reason: member with unimplementedtype ${cursor.completeStringRepr()}');
4✔
204
    _logger.warning(
4✔
205
        'Removed All $className Members from ${compound.name}(${compound.originalName}), struct member has an unsupported type.');
6✔
206
  } else if (parsed.flexibleArrayMember) {
24✔
207
    _logger.fine(
2✔
208
        '---- Removed $className members, reason: incomplete array member ${cursor.completeStringRepr()}');
2✔
209
    _logger.warning(
2✔
210
        'Removed All $className Members from ${compound.name}(${compound.originalName}), Flexible array members not supported.');
3✔
211
  } else if (parsed.bitFieldMember) {
24✔
212
    _logger.fine(
8✔
213
        '---- Removed $className members, reason: bitfield members ${cursor.completeStringRepr()}');
8✔
214
    _logger.warning(
8✔
215
        'Removed All $className Members from ${compound.name}(${compound.originalName}), Bit Field members not supported.');
12✔
216
  } else if (parsed.dartHandleMember && config.useDartHandle) {
26✔
217
    _logger.fine(
2✔
218
        '---- Removed $className members, reason: Dart_Handle member. ${cursor.completeStringRepr()}');
2✔
219
    _logger.warning(
2✔
220
        'Removed All $className Members from ${compound.name}(${compound.originalName}), Dart_Handle member not supported.');
3✔
221
  } else if (parsed.incompleteCompoundMember) {
24✔
222
    _logger.fine(
6✔
223
        '---- Removed $className members, reason: Incomplete Nested Struct member. ${cursor.completeStringRepr()}');
6✔
224
    _logger.warning(
6✔
225
        'Removed All $className Members from ${compound.name}(${compound.originalName}), Incomplete Nested Struct member not supported.');
9✔
226
  }
227

228
  // Clear all members if declaration is incomplete.
229
  if (parsed.isIncomplete) {
24✔
230
    compound.members.clear();
16✔
231
  }
232

233
  // C allows empty structs/union, but it's undefined behaviour at runtine.
234
  // So we need to mark a declaration incomplete if it has no members.
235
  compound.isIncomplete = parsed.isIncomplete || compound.members.isEmpty;
96✔
236
}
237

238
/// Visitor for the struct/union cursor [CXCursorKind.CXCursor_StructDecl]/
239
/// [CXCursorKind.CXCursor_UnionDecl].
240
///
241
/// Child visitor invoked on struct/union cursor.
242
int _compoundMembersVisitor(clang_types.CXCursor cursor,
23✔
243
    clang_types.CXCursor parent, Pointer<Void> clientData) {
244
  final parsed = _stack.top;
46✔
245
  try {
246
    switch (cursor.kind) {
23✔
247
      case clang_types.CXCursorKind.CXCursor_FieldDecl:
23✔
248
        _logger.finer('===== member: ${cursor.completeStringRepr()}');
92✔
249

250
        // Set maxChildAlignValue.
251
        final align = cursor.type().alignment();
46✔
252
        if (align > parsed.maxChildAlignment) {
46✔
253
          parsed.maxChildAlignment = align;
23✔
254
        }
255

256
        final mt = cursor.type().toCodeGenType();
46✔
257
        if (mt is IncompleteArray) {
23✔
258
          // TODO(68): Structs with flexible Array Members are not supported.
259
          parsed.flexibleArrayMember = true;
1✔
260
        }
261
        if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
92✔
262
          // TODO(84): Struct with bitfields are not suppoorted.
263
          parsed.bitFieldMember = true;
4✔
264
        }
265
        if (mt is HandleType) {
23✔
266
          parsed.dartHandleMember = true;
1✔
267
        }
268
        if (mt.isIncompleteCompound) {
23✔
269
          parsed.incompleteCompoundMember = true;
3✔
270
        }
271
        if (mt.baseType is UnimplementedType) {
46✔
272
          parsed.unimplementedMemberType = true;
2✔
273
        }
274

275
        parsed.compound.members.add(
69✔
276
          Member(
23✔
277
            dartDoc: getCursorDocComment(
23✔
278
              cursor,
279
              nesting.length + commentPrefix.length,
69✔
280
            ),
281
            originalName: cursor.spelling(),
23✔
282
            name: config.structDecl.renameMemberUsingConfig(
69✔
283
              parsed.compound.originalName,
46✔
284
              cursor.spelling(),
23✔
285
            ),
286
            type: mt,
287
          ),
288
        );
289

290
        break;
291

292
      case clang_types.CXCursorKind.CXCursor_PackedAttr:
6✔
293
        parsed.hasPackedAttr = true;
2✔
294

295
        break;
296
      case clang_types.CXCursorKind.CXCursor_UnionDecl:
6✔
297
      case clang_types.CXCursorKind.CXCursor_StructDecl:
6✔
298
        final mt = cursor.type().toCodeGenType();
4✔
299

300
        // If the union/struct are anonymous, then we need to add them now,
301
        // otherwise they will be added in the next iteration.
302
        if (!cursor.isAnonymousRecordDecl()) break;
2✔
303

304
        parsed.compound.members.add(
3✔
305
          Member(
1✔
306
            dartDoc: getCursorDocComment(
1✔
307
              cursor,
308
              nesting.length + commentPrefix.length,
3✔
309
            ),
310
            originalName: cursor.spelling(),
1✔
311
            name: config.structDecl.renameMemberUsingConfig(
3✔
312
              parsed.compound.originalName,
2✔
313
              cursor.spelling(),
1✔
314
            ),
315
            type: mt,
316
          ),
317
        );
318

319
        break;
320
    }
321
  } catch (e, s) {
322
    _logger.severe(e);
×
323
    _logger.severe(s);
×
324
    rethrow;
325
  }
326
  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
327
}
328

329
String _compoundTypeDebugName(CompoundType compoundType) {
24✔
330
  return compoundType == CompoundType.struct ? "Struct" : "Union";
24✔
331
}
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