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

dart-lang / ffigen / 4841439135

30 Apr 2023 12:19AM UTC coverage: 92.376%. Remained the same
4841439135

push

github

GitHub
Merge stable to master, bump version (#561)

7 of 7 new or added lines in 1 file covered. (100.0%)

3235 of 3502 relevant lines covered (92.38%)

25.26 hits per line

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

89.3
/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
    var res = clang.clang_getCursorUSR(this).toStringAndDispose();
96✔
64
    if (isAnonymousRecordDecl()) {
32✔
65
      res += "@offset:${sourceFileOffset()}";
6✔
66
    }
67
    return res;
68
  }
69

70
  /// Returns the kind int from [clang_types.CXCursorKind].
71
  int kind() {
32✔
72
    return clang.clang_getCursorKind(this);
64✔
73
  }
74

75
  /// Name of the cursor (E.g function name, Struct name, Parameter name).
76
  String spelling() {
32✔
77
    return clang.clang_getCursorSpelling(this).toStringAndDispose();
96✔
78
  }
79

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

87
  /// Get code_gen [Type] representation of [clang_types.CXType].
88
  Type toCodeGenType() {
28✔
89
    return getCodeGenType(type(), originalCursor: this);
56✔
90
  }
91

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

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

106
  /// Determine whether the given cursor
107
  /// represents an anonymous record declaration.
108
  bool isAnonymousRecordDecl() {
32✔
109
    return clang.clang_Cursor_isAnonymousRecordDecl(this) == 1;
96✔
110
  }
111

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

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

123
    // Puts the values in these pointers.
124
    clang.clang_getFileLocation(cxsource, cxfilePtr, nullptr, nullptr, nullptr);
160✔
125
    final s = clang.clang_getFileName(cxfilePtr.value).toStringAndDispose();
128✔
126

127
    calloc.free(cxfilePtr);
32✔
128
    return s;
129
  }
130

131
  int sourceFileOffset() {
2✔
132
    final cxsource = clang.clang_getCursorLocation(this);
4✔
133
    final cxOffset = calloc<UnsignedInt>();
134

135
    // Puts the values in these pointers.
136
    clang.clang_getFileLocation(cxsource, nullptr, nullptr, nullptr, cxOffset);
10✔
137
    final offset = cxOffset.value;
138
    calloc.free(cxOffset);
2✔
139
    return offset;
140
  }
141

142
  /// Returns whether the file that the cursor is inside is a system header.
143
  bool isInSystemHeader() {
×
144
    final location = clang.clang_getCursorLocation(this);
×
145
    return clang.clang_Location_isInSystemHeader(location) != 0;
×
146
  }
147

148
  /// Recursively print the AST, for debugging.
149
  void printAst([int maxDepth = 3]) {
×
150
    _printAstVisitorMaxDepth = maxDepth;
151
    _printAstVisitor(this, this, Pointer<Void>.fromAddress(0));
×
152
  }
153
}
154

155
Pointer<
156
        NativeFunction<
157
            Int32 Function(
158
                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
159
    _printAstVisitorPtr;
160
int _printAstVisitorMaxDepth = 0;
161
int _printAstVisitor(clang_types.CXCursor cursor, clang_types.CXCursor parent,
×
162
    Pointer<Void> clientData) {
163
  final depth = clientData.address;
×
164
  if (depth > _printAstVisitorMaxDepth) {
×
165
    return clang_types.CXChildVisitResult.CXChildVisit_Break;
166
  }
167
  print(('  ' * depth) + cursor.completeStringRepr());
×
168
  clang.clang_visitChildren(
×
169
      cursor,
170
      _printAstVisitorPtr ??=
171
          Pointer.fromFunction(_printAstVisitor, exceptional_visitor_return),
172
      Pointer<Void>.fromAddress(depth + 1));
×
173
  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
174
}
175

176
const commentPrefix = '/// ';
177
const nesting = '  ';
178

179
/// Stores the [clang_types.CXSourceRange] of the last comment.
180
clang_types.CXSourceRange? lastCommentRange;
181

182
/// Returns a cursor's associated comment.
183
///
184
/// The given string is wrapped at line width = 80 - [indent]. The [indent] is
185
/// [commentPrefix.dimensions] by default because a comment starts with
186
/// [commentPrefix].
187
String? getCursorDocComment(clang_types.CXCursor cursor,
30✔
188
    [int indent = commentPrefix.length]) {
189
  String? formattedDocComment;
190
  final currentCommentRange = clang.clang_Cursor_getCommentRange(cursor);
60✔
191

192
  // See if this comment and the last comment both point to the same source
193
  // range.
194
  if (lastCommentRange != null &&
195
      clang.clang_equalRanges(lastCommentRange!, currentCommentRange) != 0) {
84✔
196
    formattedDocComment = null;
197
  } else {
198
    switch (config.commentType.length) {
90✔
199
      case CommentLength.full:
30✔
200
        formattedDocComment = removeRawCommentMarkups(
29✔
201
            clang.clang_Cursor_getRawCommentText(cursor).toStringAndDispose());
87✔
202
        break;
203
      case CommentLength.brief:
2✔
204
        formattedDocComment = _wrapNoNewLineString(
1✔
205
            clang.clang_Cursor_getBriefCommentText(cursor).toStringAndDispose(),
3✔
206
            80 - indent);
1✔
207
        break;
208
      default:
209
        formattedDocComment = null;
210
    }
211
  }
212
  lastCommentRange = currentCommentRange;
213
  return formattedDocComment;
214
}
215

216
/// Wraps [string] according to given [lineWidth].
217
///
218
/// Wrapping will work properly only when String has no new lines
219
/// characters(\n).
220
String? _wrapNoNewLineString(String? string, int lineWidth) {
1✔
221
  if (string == null || string.isEmpty) {
1✔
222
    return null;
223
  }
224
  final sb = StringBuffer();
1✔
225

226
  final words = string.split(' ');
1✔
227

228
  sb.write(words[0]);
2✔
229
  var trackLineWidth = words[0].length;
2✔
230
  for (var i = 1; i < words.length; i++) {
3✔
231
    final word = words[i];
1✔
232
    if (trackLineWidth + word.length < lineWidth) {
3✔
233
      sb.write(' ');
1✔
234
      sb.write(word);
1✔
235
      trackLineWidth += word.length + 1;
3✔
236
    } else {
237
      sb.write('\n');
1✔
238
      sb.write(word);
1✔
239
      trackLineWidth = word.length;
1✔
240
    }
241
  }
242
  return sb.toString();
1✔
243
}
244

245
/// Removes /*, */ and any *'s in the beginning of a line.
246
String? removeRawCommentMarkups(String? string) {
29✔
247
  if (string == null || string.isEmpty) {
29✔
248
    return null;
249
  }
250
  final sb = StringBuffer();
7✔
251

252
  // Remove comment identifiers (`/** * */`, `///`, `//`) from lines.
253
  if (string.contains(RegExp(r'^\s*\/\*+'))) {
14✔
254
    string = string.replaceFirst(RegExp(r'^\s*\/\*+\s*'), '');
10✔
255
    string = string.replaceFirst(RegExp(r'\s*\*+\/$'), '');
10✔
256
    string.split('\n').forEach((element) {
15✔
257
      element = element.replaceFirst(RegExp(r'^\s*\**\s*'), '');
10✔
258
      sb.writeln(element);
5✔
259
    });
260
  } else if (string.contains(RegExp(r'^\s*\/\/\/?\s*'))) {
6✔
261
    string.split('\n').forEach((element) {
9✔
262
      element = element.replaceFirst(RegExp(r'^\s*\/\/\/?\s*'), '');
6✔
263
      sb.writeln(element);
3✔
264
    });
265
  }
266

267
  return sb.toString().trim();
14✔
268
}
269

270
extension CXTypeExt on clang_types.CXType {
271
  /// Get code_gen [Type] representation of [clang_types.CXType].
272
  Type toCodeGenType() {
27✔
273
    return getCodeGenType(this);
27✔
274
  }
275

276
  /// Spelling for a [clang_types.CXTypeKind], useful for debug purposes.
277
  String spelling() {
32✔
278
    return clang.clang_getTypeSpelling(this).toStringAndDispose();
96✔
279
  }
280

281
  /// Returns the typeKind int from [clang_types.CXTypeKind].
282
  int kind() {
31✔
283
    return this.kind;
31✔
284
  }
285

286
  String kindSpelling() {
31✔
287
    return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose();
124✔
288
  }
289

290
  int alignment() {
25✔
291
    return clang.clang_Type_getAlignOf(this);
50✔
292
  }
293

294
  /// For debugging: returns [spelling] [kind] [kindSpelling].
295
  String completeStringRepr() {
31✔
296
    final s =
297
        '(Type) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}';
124✔
298
    return s;
299
  }
300
}
301

302
extension CXStringExt on clang_types.CXString {
303
  /// Convert CXString to a Dart string
304
  ///
305
  /// Make sure to dispose CXstring using dispose method, or use the
306
  /// [toStringAndDispose] method.
307
  String string() {
32✔
308
    final cstring = clang.clang_getCString(this);
64✔
309
    if (cstring != nullptr) {
64✔
310
      return cstring.cast<Utf8>().toDartString();
64✔
311
    } else {
312
      return '';
313
    }
314
  }
315

316
  /// Converts CXString to dart string and disposes CXString.
317
  String toStringAndDispose() {
32✔
318
    // Note: clang_getCString_wrap returns a const char *, calling free will result in error.
319
    final s = string();
32✔
320
    clang.clang_disposeString(this);
64✔
321
    return s;
322
  }
323

324
  void dispose() {
×
325
    clang.clang_disposeString(this);
×
326
  }
327
}
328

329
/// Converts a [List<String>] to [Pointer<Pointer<Utf8>>].
330
Pointer<Pointer<Utf8>> createDynamicStringArray(List<String> list) {
47✔
331
  final nativeCmdArgs = calloc<Pointer<Utf8>>(list.length);
47✔
332

333
  for (var i = 0; i < list.length; i++) {
141✔
334
    nativeCmdArgs[i] = list[i].toNativeUtf8();
141✔
335
  }
336

337
  return nativeCmdArgs;
338
}
339

340
extension DynamicCStringArray on Pointer<Pointer<Utf8>> {
341
  // Properly disposes a Pointer<Pointer<Utf8>, ensure that sure length is correct.
342
  void dispose(int length) {
47✔
343
    for (var i = 0; i < length; i++) {
94✔
344
      calloc.free(this[i]);
94✔
345
    }
346
    calloc.free(this);
47✔
347
  }
348
}
349

350
class Stack<T> {
351
  final _stack = <T>[];
352

353
  T get top => _stack.last;
87✔
354
  T pop() => _stack.removeLast();
93✔
355
  void push(T item) => _stack.add(item);
93✔
356
}
357

358
class IncrementalNamer {
359
  final _incrementedStringCounters = <String, int>{};
360

361
  /// Appends `<int>` to base. <int> is incremented on every call.
362
  String name(String base) {
2✔
363
    var i = _incrementedStringCounters[base] ?? 0;
4✔
364
    i++;
2✔
365
    _incrementedStringCounters[base] = i;
4✔
366
    return '$base$i';
2✔
367
  }
368
}
369

370
class Macro {
371
  final String usr;
372
  final String? originalName;
373

374
  Macro(this.usr, this.originalName);
9✔
375
}
376

377
/// Tracks if a binding is 'seen' or not.
378
class BindingsIndex {
379
  // Tracks if bindings are already seen, Map key is USR obtained from libclang.
380
  final Map<String, Type> _declaredTypes = {};
381
  final Map<String, Func> _functions = {};
382
  final Map<String, Constant> _unnamedEnumConstants = {};
383
  final Map<String, String> _macros = {};
384
  final Map<String, Global> _globals = {};
385
  final Map<String, ObjCBlock> _objcBlocks = {};
386

387
  /// Contains usr for typedefs which cannot be generated.
388
  final Set<String> _unsupportedTypealiases = {};
389

390
  /// Index for headers.
391
  final Map<String, bool> _headerCache = {};
392

393
  bool isSeenType(String usr) => _declaredTypes.containsKey(usr);
81✔
394
  void addTypeToSeen(String usr, Type type) => _declaredTypes[usr] = type;
81✔
395
  Type? getSeenType(String usr) => _declaredTypes[usr];
84✔
396
  bool isSeenFunc(String usr) => _functions.containsKey(usr);
72✔
397
  void addFuncToSeen(String usr, Func func) => _functions[usr] = func;
66✔
398
  Func? getSeenFunc(String usr) => _functions[usr];
3✔
399
  bool isSeenUnnamedEnumConstant(String usr) =>
6✔
400
      _unnamedEnumConstants.containsKey(usr);
12✔
401
  void addUnnamedEnumConstantToSeen(String usr, Constant enumConstant) =>
4✔
402
      _unnamedEnumConstants[usr] = enumConstant;
8✔
403
  Constant? getSeenUnnamedEnumConstant(String usr) =>
×
404
      _unnamedEnumConstants[usr];
×
405
  bool isSeenGlobalVar(String usr) => _globals.containsKey(usr);
18✔
406
  void addGlobalVarToSeen(String usr, Global global) => _globals[usr] = global;
12✔
407
  Global? getSeenGlobalVar(String usr) => _globals[usr];
×
408
  bool isSeenMacro(String usr) => _macros.containsKey(usr);
33✔
409
  void addMacroToSeen(String usr, String macro) => _macros[usr] = macro;
27✔
410
  String? getSeenMacro(String usr) => _macros[usr];
×
411
  bool isSeenUnsupportedTypealias(String usr) =>
16✔
412
      _unsupportedTypealiases.contains(usr);
32✔
413
  void addUnsupportedTypealiasToSeen(String usr) =>
6✔
414
      _unsupportedTypealiases.add(usr);
12✔
415
  bool isSeenHeader(String source) => _headerCache.containsKey(source);
96✔
416
  void addHeaderToSeen(String source, bool includeStatus) =>
32✔
417
      _headerCache[source] = includeStatus;
64✔
418
  bool? getSeenHeaderStatus(String source) => _headerCache[source];
96✔
419
  void addObjCBlockToSeen(String key, ObjCBlock t) => _objcBlocks[key] = t;
6✔
420
  ObjCBlock? getSeenObjCBlock(String key) => _objcBlocks[key];
6✔
421
}
422

423
class CursorIndex {
424
  final _usrCursorDefinition = <String, clang_types.CXCursor>{};
425

426
  /// Returns the Cursor definition (if found) or itself.
427
  clang_types.CXCursor getDefinition(clang_types.CXCursor cursor) {
26✔
428
    final cursorDefinition = clang.clang_getCursorDefinition(cursor);
52✔
429
    if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
78✔
430
      return cursorDefinition;
431
    } else {
432
      final usr = cursor.usr();
6✔
433
      if (_usrCursorDefinition.containsKey(usr)) {
12✔
434
        return _usrCursorDefinition[cursor.usr()]!;
3✔
435
      } else {
436
        _logger.warning(
10✔
437
            "No definition found for declaration - ${cursor.completeStringRepr()}");
10✔
438
        return cursor;
439
      }
440
    }
441
  }
442

443
  /// Saves cursor definition based on its kind.
444
  void saveDefinition(clang_types.CXCursor cursor) {
32✔
445
    switch (cursor.kind) {
32✔
446
      case clang_types.CXCursorKind.CXCursor_StructDecl:
32✔
447
      case clang_types.CXCursorKind.CXCursor_UnionDecl:
32✔
448
      case clang_types.CXCursorKind.CXCursor_EnumDecl:
32✔
449
        final usr = cursor.usr();
27✔
450
        if (!_usrCursorDefinition.containsKey(usr)) {
56✔
451
          final cursorDefinition = clang.clang_getCursorDefinition(cursor);
54✔
452
          if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
81✔
453
            _usrCursorDefinition[usr] = cursorDefinition;
56✔
454
          } else {
455
            _logger.finest(
8✔
456
                "Missing cursor definition in current translation unit: ${cursor.completeStringRepr()}");
8✔
457
          }
458
        }
459
    }
460
  }
461
}
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