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

dart-lang / ffigen / 4364636722

pending completion
4364636722

Pull #527

github

GitHub
Merge 984472b2c into 1ac20ffd5
Pull Request #527: Bump dependencies and fix lints

21 of 21 new or added lines in 10 files covered. (100.0%)

3188 of 3452 relevant lines covered (92.35%)

24.67 hits per line

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

88.83
/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) {
31✔
25
  if (resultCode != exceptional_visitor_return) {
31✔
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(
31✔
33
    Pointer<clang_types.CXTranslationUnitImpl> tu, Logger logger, String header,
34
    {Level logLevel = Level.SEVERE}) {
35
  final total = clang.clang_getNumDiagnostics(tu);
62✔
36
  if (total == 0) {
31✔
37
    return;
38
  }
39
  logger.log(logLevel, 'Header $header: Total errors/warnings: $total.');
10✔
40
  for (var i = 0; i < total; i++) {
10✔
41
    final diag = clang.clang_getDiagnostic(tu, i);
10✔
42
    final cxstring = clang.clang_formatDiagnostic(
10✔
43
      diag,
44
      clang_types
45
              .CXDiagnosticDisplayOptions.CXDiagnostic_DisplaySourceLocation |
5✔
46
          clang_types.CXDiagnosticDisplayOptions.CXDiagnostic_DisplayColumn |
5✔
47
          clang_types
48
              .CXDiagnosticDisplayOptions.CXDiagnostic_DisplayCategoryName,
49
    );
50
    logger.log(logLevel, '    ${cxstring.toStringAndDispose()}');
15✔
51
    clang.clang_disposeDiagnostic(diag);
10✔
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() {
31✔
63
    return clang.clang_getCursorUSR(this).toStringAndDispose();
93✔
64
  }
65

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

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

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

83
  /// for debug: returns [spelling] [kind] [kindSpelling] [type] [typeSpelling].
84
  String completeStringRepr() {
31✔
85
    final cxtype = type();
31✔
86
    final s =
87
        '(Cursor) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}, type: ${cxtype.kind}, typeSpelling: ${cxtype.spelling()}, usr: ${usr()}';
217✔
88
    return s;
89
  }
90

91
  /// Type associated with the pointer if any. Type will have kind
92
  /// [clang.CXTypeKind.CXType_Invalid] otherwise.
93
  clang_types.CXType type() {
31✔
94
    return clang.clang_getCursorType(this);
62✔
95
  }
96

97
  /// Determine whether the given cursor
98
  /// represents an anonymous record declaration.
99
  bool isAnonymousRecordDecl() {
2✔
100
    return clang.clang_Cursor_isAnonymousRecordDecl(this) == 1;
6✔
101
  }
102

103
  /// Only valid for [clang.CXCursorKind.CXCursor_FunctionDecl]. Type will have
104
  /// kind [clang.CXTypeKind.CXType_Invalid] otherwise.
105
  clang_types.CXType returnType() {
22✔
106
    return clang.clang_getResultType(type());
66✔
107
  }
108

109
  /// Returns the file name of the file that the cursor is inside.
110
  String sourceFileName() {
31✔
111
    final cxsource = clang.clang_getCursorLocation(this);
62✔
112
    final cxfilePtr = calloc<Pointer<Void>>();
113

114
    // Puts the values in these pointers.
115
    clang.clang_getFileLocation(cxsource, cxfilePtr, nullptr, nullptr, nullptr);
155✔
116
    final s = clang.clang_getFileName(cxfilePtr.value).toStringAndDispose();
124✔
117

118
    calloc.free(cxfilePtr);
31✔
119
    return s;
120
  }
121

122
  /// Returns whether the file that the cursor is inside is a system header.
123
  bool isInSystemHeader() {
×
124
    final location = clang.clang_getCursorLocation(this);
×
125
    return clang.clang_Location_isInSystemHeader(location) != 0;
×
126
  }
127

128
  /// Recursively print the AST, for debugging.
129
  void printAst([int maxDepth = 3]) {
×
130
    _printAstVisitorMaxDepth = maxDepth;
131
    _printAstVisitor(this, this, Pointer<Void>.fromAddress(0));
×
132
  }
133
}
134

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

156
const commentPrefix = '/// ';
157
const nesting = '  ';
158

159
/// Stores the [clang_types.CXSourceRange] of the last comment.
160
clang_types.CXSourceRange? lastCommentRange;
161

162
/// Returns a cursor's associated comment.
163
///
164
/// The given string is wrapped at line width = 80 - [indent]. The [indent] is
165
/// [commentPrefix.dimensions] by default because a comment starts with
166
/// [commentPrefix].
167
String? getCursorDocComment(clang_types.CXCursor cursor,
29✔
168
    [int indent = commentPrefix.length]) {
169
  String? formattedDocComment;
170
  final currentCommentRange = clang.clang_Cursor_getCommentRange(cursor);
58✔
171

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

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

206
  final words = string.split(' ');
1✔
207

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

225
/// Removes /*, */ and any *'s in the beginning of a line.
226
String? removeRawCommentMarkups(String? string) {
28✔
227
  if (string == null || string.isEmpty) {
28✔
228
    return null;
229
  }
230
  final sb = StringBuffer();
7✔
231

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

247
  return sb.toString().trim();
14✔
248
}
249

250
extension CXTypeExt on clang_types.CXType {
251
  /// Get code_gen [Type] representation of [clang_types.CXType].
252
  Type toCodeGenType() {
29✔
253
    return getCodeGenType(this);
29✔
254
  }
255

256
  /// Spelling for a [clang_types.CXTypeKind], useful for debug purposes.
257
  String spelling() {
31✔
258
    return clang.clang_getTypeSpelling(this).toStringAndDispose();
93✔
259
  }
260

261
  /// Returns the typeKind int from [clang_types.CXTypeKind].
262
  int kind() {
30✔
263
    return this.kind;
30✔
264
  }
265

266
  String kindSpelling() {
30✔
267
    return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose();
120✔
268
  }
269

270
  int alignment() {
24✔
271
    return clang.clang_Type_getAlignOf(this);
48✔
272
  }
273

274
  /// For debugging: returns [spelling] [kind] [kindSpelling].
275
  String completeStringRepr() {
30✔
276
    final s =
277
        '(Type) spelling: ${spelling()}, kind: ${kind()}, kindSpelling: ${kindSpelling()}';
120✔
278
    return s;
279
  }
280
}
281

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

296
  /// Converts CXString to dart string and disposes CXString.
297
  String toStringAndDispose() {
31✔
298
    // Note: clang_getCString_wrap returns a const char *, calling free will result in error.
299
    final s = string();
31✔
300
    clang.clang_disposeString(this);
62✔
301
    return s;
302
  }
303

304
  void dispose() {
×
305
    clang.clang_disposeString(this);
×
306
  }
307
}
308

309
/// Converts a [List<String>] to [Pointer<Pointer<Utf8>>].
310
Pointer<Pointer<Utf8>> createDynamicStringArray(List<String> list) {
46✔
311
  final nativeCmdArgs = calloc<Pointer<Utf8>>(list.length);
46✔
312

313
  for (var i = 0; i < list.length; i++) {
138✔
314
    nativeCmdArgs[i] = list[i].toNativeUtf8();
138✔
315
  }
316

317
  return nativeCmdArgs;
318
}
319

320
extension DynamicCStringArray on Pointer<Pointer<Utf8>> {
321
  // Properly disposes a Pointer<Pointer<Utf8>, ensure that sure length is correct.
322
  void dispose(int length) {
46✔
323
    for (var i = 0; i < length; i++) {
92✔
324
      calloc.free(this[i]);
92✔
325
    }
326
    calloc.free(this);
46✔
327
  }
328
}
329

330
class Stack<T> {
331
  final _stack = <T>[];
332

333
  T get top => _stack.last;
84✔
334
  T pop() => _stack.removeLast();
90✔
335
  void push(T item) => _stack.add(item);
90✔
336
}
337

338
class IncrementalNamer {
339
  final _incrementedStringCounters = <String, int>{};
340

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

350
class Macro {
351
  final String usr;
352
  final String? originalName;
353

354
  Macro(this.usr, this.originalName);
9✔
355
}
356

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

367
  /// Contains usr for typedefs which cannot be generated.
368
  final Set<String> _unsupportedTypealiases = {};
369

370
  /// Index for headers.
371
  final Map<String, bool> _headerCache = {};
372

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

403
class CursorIndex {
404
  final _usrCursorDefinition = <String, clang_types.CXCursor>{};
405

406
  /// Returns the Cursor definition (if found) or itself.
407
  clang_types.CXCursor getDefinition(clang_types.CXCursor cursor) {
25✔
408
    final cursorDefinition = clang.clang_getCursorDefinition(cursor);
50✔
409
    if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
75✔
410
      return cursorDefinition;
411
    } else {
412
      final usr = cursor.usr();
6✔
413
      if (_usrCursorDefinition.containsKey(usr)) {
12✔
414
        return _usrCursorDefinition[cursor.usr()]!;
3✔
415
      } else {
416
        _logger.warning(
10✔
417
            "No definition found for declaration - ${cursor.completeStringRepr()}");
10✔
418
        return cursor;
419
      }
420
    }
421
  }
422

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