• 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

88.95
/lib/src/header_parser/utils.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
import 'dart:ffi';
6

7
import 'package:ffi/ffi.dart';
8
import 'package:ffigen/src/code_generator.dart';
9
import 'package:ffigen/src/config_provider/config_types.dart';
10
import 'package:logging/logging.dart';
11

12
import 'clang_bindings/clang_bindings.dart' as clang_types;
13
import 'data.dart';
14
import 'type_extractor/extractor.dart';
15

16
final _logger = Logger('ffigen.header_parser.utils');
18✔
17

18
const exceptional_visitor_return =
19
    clang_types.CXChildVisitResult.CXChildVisit_Break;
20

21
/// Check [resultCode] of [clang.clang_visitChildren_wrap].
22
///
23
/// Throws exception if resultCode is not [exceptional_visitor_return].
24
void visitChildrenResultChecker(int resultCode) {
32✔
25
  if (resultCode != exceptional_visitor_return) {
32✔
26
    throw Exception(
×
27
        'Exception thrown in a dart function called via C, use --verbose to see more details');
28
  }
29
}
30

31
/// Logs the warnings/errors returned by clang for a translation unit.
32
void logTuDiagnostics(
32✔
33
    Pointer<clang_types.CXTranslationUnitImpl> tu, Logger logger, String header,
34
    {Level logLevel = Level.SEVERE}) {
35
  final total = clang.clang_getNumDiagnostics(tu);
64✔
36
  if (total == 0) {
32✔
37
    return;
38
  }
39
  logger.log(logLevel, 'Header $header: Total errors/warnings: $total.');
18✔
40
  for (var i = 0; i < total; i++) {
18✔
41
    final diag = clang.clang_getDiagnostic(tu, i);
18✔
42
    final cxstring = clang.clang_formatDiagnostic(
18✔
43
      diag,
44
      clang_types
45
              .CXDiagnosticDisplayOptions.CXDiagnostic_DisplaySourceLocation |
9✔
46
          clang_types.CXDiagnosticDisplayOptions.CXDiagnostic_DisplayColumn |
9✔
47
          clang_types
48
              .CXDiagnosticDisplayOptions.CXDiagnostic_DisplayCategoryName,
49
    );
50
    logger.log(logLevel, '    ${cxstring.toStringAndDispose()}');
27✔
51
    clang.clang_disposeDiagnostic(diag);
18✔
52
  }
53
}
54

55
extension CXSourceRangeExt on Pointer<clang_types.CXSourceRange> {
56
  void dispose() {
×
57
    calloc.free(this);
×
58
  }
59
}
60

61
extension CXCursorExt on clang_types.CXCursor {
62
  String usr() {
32✔
63
    return clang.clang_getCursorUSR(this).toStringAndDispose();
96✔
64
  }
65

66
  /// Returns the kind int from [clang_types.CXCursorKind].
67
  int kind() {
32✔
68
    return clang.clang_getCursorKind(this);
64✔
69
  }
70

71
  /// Name of the cursor (E.g function name, Struct name, Parameter name).
72
  String spelling() {
32✔
73
    return clang.clang_getCursorSpelling(this).toStringAndDispose();
96✔
74
  }
75

76
  /// Spelling for a [clang_types.CXCursorKind], useful for debug purposes.
77
  String kindSpelling() {
32✔
78
    return clang
32✔
79
        .clang_getCursorKindSpelling(clang.clang_getCursorKind(this))
96✔
80
        .toStringAndDispose();
32✔
81
  }
82

83
  /// Get code_gen [Type] representation of [clang_types.CXType].
84
  Type toCodeGenType() {
28✔
85
    return getCodeGenType(type(), originalCursor: this);
56✔
86
  }
87

88
  /// for debug: returns [spelling] [kind] [kindSpelling] [type] [typeSpelling].
89
  String completeStringRepr() {
32✔
90
    final cxtype = type();
32✔
91
    final s =
92
        '(Cursor) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}, type: ${cxtype.kind}, typeSpelling: ${cxtype.spelling()}, usr: ${usr()}';
224✔
93
    return s;
94
  }
95

96
  /// Type associated with the pointer if any. Type will have kind
97
  /// [clang.CXTypeKind.CXType_Invalid] otherwise.
98
  clang_types.CXType type() {
32✔
99
    return clang.clang_getCursorType(this);
64✔
100
  }
101

102
  /// Determine whether the given cursor
103
  /// represents an anonymous record declaration.
104
  bool isAnonymousRecordDecl() {
2✔
105
    return clang.clang_Cursor_isAnonymousRecordDecl(this) == 1;
6✔
106
  }
107

108
  /// Only valid for [clang.CXCursorKind.CXCursor_FunctionDecl]. Type will have
109
  /// kind [clang.CXTypeKind.CXType_Invalid] otherwise.
110
  clang_types.CXType returnType() {
22✔
111
    return clang.clang_getResultType(type());
66✔
112
  }
113

114
  /// Returns the file name of the file that the cursor is inside.
115
  String sourceFileName() {
32✔
116
    final cxsource = clang.clang_getCursorLocation(this);
64✔
117
    final cxfilePtr = calloc<Pointer<Void>>();
118

119
    // Puts the values in these pointers.
120
    clang.clang_getFileLocation(cxsource, cxfilePtr, nullptr, nullptr, nullptr);
160✔
121
    final s = clang.clang_getFileName(cxfilePtr.value).toStringAndDispose();
128✔
122

123
    calloc.free(cxfilePtr);
32✔
124
    return s;
125
  }
126

127
  /// Returns whether the file that the cursor is inside is a system header.
128
  bool isInSystemHeader() {
×
129
    final location = clang.clang_getCursorLocation(this);
×
130
    return clang.clang_Location_isInSystemHeader(location) != 0;
×
131
  }
132

133
  /// Recursively print the AST, for debugging.
134
  void printAst([int maxDepth = 3]) {
×
135
    _printAstVisitorMaxDepth = maxDepth;
136
    _printAstVisitor(this, this, Pointer<Void>.fromAddress(0));
×
137
  }
138
}
139

140
Pointer<
141
        NativeFunction<
142
            Int32 Function(
143
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
144
    _printAstVisitorPtr;
145
int _printAstVisitorMaxDepth = 0;
146
int _printAstVisitor(clang_types.CXCursor cursor, clang_types.CXCursor parent,
×
147
    Pointer<Void> clientData) {
148
  final depth = clientData.address;
×
149
  if (depth > _printAstVisitorMaxDepth) {
×
150
    return clang_types.CXChildVisitResult.CXChildVisit_Break;
151
  }
152
  print(('  ' * depth) + cursor.completeStringRepr());
×
153
  clang.clang_visitChildren(
×
154
      cursor,
155
      _printAstVisitorPtr ??=
156
          Pointer.fromFunction(_printAstVisitor, exceptional_visitor_return),
157
      Pointer<Void>.fromAddress(depth + 1));
×
158
  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
159
}
160

161
const commentPrefix = '/// ';
162
const nesting = '  ';
163

164
/// Stores the [clang_types.CXSourceRange] of the last comment.
165
clang_types.CXSourceRange? lastCommentRange;
166

167
/// Returns a cursor's associated comment.
168
///
169
/// The given string is wrapped at line width = 80 - [indent]. The [indent] is
170
/// [commentPrefix.dimensions] by default because a comment starts with
171
/// [commentPrefix].
172
String? getCursorDocComment(clang_types.CXCursor cursor,
30✔
173
    [int indent = commentPrefix.length]) {
174
  String? formattedDocComment;
175
  final currentCommentRange = clang.clang_Cursor_getCommentRange(cursor);
60✔
176

177
  // See if this comment and the last comment both point to the same source
178
  // range.
179
  if (lastCommentRange != null &&
180
      clang.clang_equalRanges(lastCommentRange!, currentCommentRange) != 0) {
84✔
181
    formattedDocComment = null;
182
  } else {
183
    switch (config.commentType.length) {
90✔
184
      case CommentLength.full:
30✔
185
        formattedDocComment = removeRawCommentMarkups(
29✔
186
            clang.clang_Cursor_getRawCommentText(cursor).toStringAndDispose());
87✔
187
        break;
188
      case CommentLength.brief:
2✔
189
        formattedDocComment = _wrapNoNewLineString(
1✔
190
            clang.clang_Cursor_getBriefCommentText(cursor).toStringAndDispose(),
3✔
191
            80 - indent);
1✔
192
        break;
193
      default:
194
        formattedDocComment = null;
195
    }
196
  }
197
  lastCommentRange = currentCommentRange;
198
  return formattedDocComment;
199
}
200

201
/// Wraps [string] according to given [lineWidth].
202
///
203
/// Wrapping will work properly only when String has no new lines
204
/// characters(\n).
205
String? _wrapNoNewLineString(String? string, int lineWidth) {
1✔
206
  if (string == null || string.isEmpty) {
1✔
207
    return null;
208
  }
209
  final sb = StringBuffer();
1✔
210

211
  final words = string.split(' ');
1✔
212

213
  sb.write(words[0]);
2✔
214
  var trackLineWidth = words[0].length;
2✔
215
  for (var i = 1; i < words.length; i++) {
3✔
216
    final word = words[i];
1✔
217
    if (trackLineWidth + word.length < lineWidth) {
3✔
218
      sb.write(' ');
1✔
219
      sb.write(word);
1✔
220
      trackLineWidth += word.length + 1;
3✔
221
    } else {
222
      sb.write('\n');
1✔
223
      sb.write(word);
1✔
224
      trackLineWidth = word.length;
1✔
225
    }
226
  }
227
  return sb.toString();
1✔
228
}
229

230
/// Removes /*, */ and any *'s in the beginning of a line.
231
String? removeRawCommentMarkups(String? string) {
29✔
232
  if (string == null || string.isEmpty) {
29✔
233
    return null;
234
  }
235
  final sb = StringBuffer();
7✔
236

237
  // Remove comment identifiers (`/** * */`, `///`, `//`) from lines.
238
  if (string.contains(RegExp(r'^\s*\/\*+'))) {
14✔
239
    string = string.replaceFirst(RegExp(r'^\s*\/\*+\s*'), '');
10✔
240
    string = string.replaceFirst(RegExp(r'\s*\*+\/$'), '');
10✔
241
    string.split('\n').forEach((element) {
15✔
242
      element = element.replaceFirst(RegExp(r'^\s*\**\s*'), '');
10✔
243
      sb.writeln(element);
5✔
244
    });
245
  } else if (string.contains(RegExp(r'^\s*\/\/\/?\s*'))) {
6✔
246
    string.split('\n').forEach((element) {
9✔
247
      element = element.replaceFirst(RegExp(r'^\s*\/\/\/?\s*'), '');
6✔
248
      sb.writeln(element);
3✔
249
    });
250
  }
251

252
  return sb.toString().trim();
14✔
253
}
254

255
extension CXTypeExt on clang_types.CXType {
256
  /// Get code_gen [Type] representation of [clang_types.CXType].
257
  Type toCodeGenType() {
27✔
258
    return getCodeGenType(this);
27✔
259
  }
260

261
  /// Spelling for a [clang_types.CXTypeKind], useful for debug purposes.
262
  String spelling() {
32✔
263
    return clang.clang_getTypeSpelling(this).toStringAndDispose();
96✔
264
  }
265

266
  /// Returns the typeKind int from [clang_types.CXTypeKind].
267
  int kind() {
31✔
268
    return this.kind;
31✔
269
  }
270

271
  String kindSpelling() {
31✔
272
    return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose();
124✔
273
  }
274

275
  int alignment() {
25✔
276
    return clang.clang_Type_getAlignOf(this);
50✔
277
  }
278

279
  /// For debugging: returns [spelling] [kind] [kindSpelling].
280
  String completeStringRepr() {
31✔
281
    final s =
282
        '(Type) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}';
124✔
283
    return s;
284
  }
285
}
286

287
extension CXStringExt on clang_types.CXString {
288
  /// Convert CXString to a Dart string
289
  ///
290
  /// Make sure to dispose CXstring using dispose method, or use the
291
  /// [toStringAndDispose] method.
292
  String string() {
32✔
293
    final cstring = clang.clang_getCString(this);
64✔
294
    if (cstring != nullptr) {
64✔
295
      return cstring.cast<Utf8>().toDartString();
64✔
296
    } else {
297
      return '';
298
    }
299
  }
300

301
  /// Converts CXString to dart string and disposes CXString.
302
  String toStringAndDispose() {
32✔
303
    // Note: clang_getCString_wrap returns a const char *, calling free will result in error.
304
    final s = string();
32✔
305
    clang.clang_disposeString(this);
64✔
306
    return s;
307
  }
308

309
  void dispose() {
×
310
    clang.clang_disposeString(this);
×
311
  }
312
}
313

314
/// Converts a [List<String>] to [Pointer<Pointer<Utf8>>].
315
Pointer<Pointer<Utf8>> createDynamicStringArray(List<String> list) {
47✔
316
  final nativeCmdArgs = calloc<Pointer<Utf8>>(list.length);
47✔
317

318
  for (var i = 0; i < list.length; i++) {
141✔
319
    nativeCmdArgs[i] = list[i].toNativeUtf8();
141✔
320
  }
321

322
  return nativeCmdArgs;
323
}
324

325
extension DynamicCStringArray on Pointer<Pointer<Utf8>> {
326
  // Properly disposes a Pointer<Pointer<Utf8>, ensure that sure length is correct.
327
  void dispose(int length) {
47✔
328
    for (var i = 0; i < length; i++) {
94✔
329
      calloc.free(this[i]);
94✔
330
    }
331
    calloc.free(this);
47✔
332
  }
333
}
334

335
class Stack<T> {
336
  final _stack = <T>[];
337

338
  T get top => _stack.last;
87✔
339
  T pop() => _stack.removeLast();
93✔
340
  void push(T item) => _stack.add(item);
93✔
341
}
342

343
class IncrementalNamer {
344
  final _incrementedStringCounters = <String, int>{};
345

346
  /// Appends `<int>` to base. <int> is incremented on every call.
347
  String name(String base) {
1✔
348
    var i = _incrementedStringCounters[base] ?? 0;
2✔
349
    i++;
1✔
350
    _incrementedStringCounters[base] = i;
2✔
351
    return '$base$i';
1✔
352
  }
353
}
354

355
class Macro {
356
  final String usr;
357
  final String? originalName;
358

359
  Macro(this.usr, this.originalName);
9✔
360
}
361

362
/// Tracks if a binding is 'seen' or not.
363
class BindingsIndex {
364
  // Tracks if bindings are already seen, Map key is USR obtained from libclang.
365
  final Map<String, Type> _declaredTypes = {};
366
  final Map<String, Func> _functions = {};
367
  final Map<String, Constant> _unnamedEnumConstants = {};
368
  final Map<String, String> _macros = {};
369
  final Map<String, Global> _globals = {};
370
  final Map<String, ObjCBlock> _objcBlocks = {};
371

372
  /// Contains usr for typedefs which cannot be generated.
373
  final Set<String> _unsupportedTypealiases = {};
374

375
  /// Index for headers.
376
  final Map<String, bool> _headerCache = {};
377

378
  bool isSeenType(String usr) => _declaredTypes.containsKey(usr);
81✔
379
  void addTypeToSeen(String usr, Type type) => _declaredTypes[usr] = type;
81✔
380
  Type? getSeenType(String usr) => _declaredTypes[usr];
84✔
381
  bool isSeenFunc(String usr) => _functions.containsKey(usr);
72✔
382
  void addFuncToSeen(String usr, Func func) => _functions[usr] = func;
66✔
383
  Func? getSeenFunc(String usr) => _functions[usr];
3✔
384
  bool isSeenUnnamedEnumConstant(String usr) =>
6✔
385
      _unnamedEnumConstants.containsKey(usr);
12✔
386
  void addUnnamedEnumConstantToSeen(String usr, Constant enumConstant) =>
4✔
387
      _unnamedEnumConstants[usr] = enumConstant;
8✔
388
  Constant? getSeenUnnamedEnumConstant(String usr) =>
×
389
      _unnamedEnumConstants[usr];
×
390
  bool isSeenGlobalVar(String usr) => _globals.containsKey(usr);
18✔
391
  void addGlobalVarToSeen(String usr, Global global) => _globals[usr] = global;
12✔
392
  Global? getSeenGlobalVar(String usr) => _globals[usr];
×
393
  bool isSeenMacro(String usr) => _macros.containsKey(usr);
33✔
394
  void addMacroToSeen(String usr, String macro) => _macros[usr] = macro;
27✔
395
  String? getSeenMacro(String usr) => _macros[usr];
×
396
  bool isSeenUnsupportedTypealias(String usr) =>
16✔
397
      _unsupportedTypealiases.contains(usr);
32✔
398
  void addUnsupportedTypealiasToSeen(String usr) =>
6✔
399
      _unsupportedTypealiases.add(usr);
12✔
400
  bool isSeenHeader(String source) => _headerCache.containsKey(source);
96✔
401
  void addHeaderToSeen(String source, bool includeStatus) =>
32✔
402
      _headerCache[source] = includeStatus;
64✔
403
  bool? getSeenHeaderStatus(String source) => _headerCache[source];
96✔
404
  void addObjCBlockToSeen(String key, ObjCBlock t) => _objcBlocks[key] = t;
6✔
405
  ObjCBlock? getSeenObjCBlock(String key) => _objcBlocks[key];
6✔
406
}
407

408
class CursorIndex {
409
  final _usrCursorDefinition = <String, clang_types.CXCursor>{};
410

411
  /// Returns the Cursor definition (if found) or itself.
412
  clang_types.CXCursor getDefinition(clang_types.CXCursor cursor) {
26✔
413
    final cursorDefinition = clang.clang_getCursorDefinition(cursor);
52✔
414
    if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
78✔
415
      return cursorDefinition;
416
    } else {
417
      final usr = cursor.usr();
6✔
418
      if (_usrCursorDefinition.containsKey(usr)) {
12✔
419
        return _usrCursorDefinition[cursor.usr()]!;
3✔
420
      } else {
421
        _logger.warning(
10✔
422
            "No definition found for declaration - ${cursor.completeStringRepr()}");
10✔
423
        return cursor;
424
      }
425
    }
426
  }
427

428
  /// Saves cursor definition based on its kind.
429
  void saveDefinition(clang_types.CXCursor cursor) {
32✔
430
    switch (cursor.kind) {
32✔
431
      case clang_types.CXCursorKind.CXCursor_StructDecl:
32✔
432
      case clang_types.CXCursorKind.CXCursor_UnionDecl:
32✔
433
      case clang_types.CXCursorKind.CXCursor_EnumDecl:
32✔
434
        final usr = cursor.usr();
27✔
435
        if (!_usrCursorDefinition.containsKey(usr)) {
56✔
436
          final cursorDefinition = clang.clang_getCursorDefinition(cursor);
54✔
437
          if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
81✔
438
            _usrCursorDefinition[usr] = cursorDefinition;
56✔
439
          } else {
440
            _logger.finest(
8✔
441
                "Missing cursor definition in current translation unit: ${cursor.completeStringRepr()}");
8✔
442
          }
443
        }
444
    }
445
  }
446
}
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