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

dart-lang / ffigen / 4272458024

pending completion
4272458024

push

github

GitHub
Bump SDK constraint to 4.0.0 (#517)

3164 of 3442 relevant lines covered (91.92%)

24.71 hits per line

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

84.36
/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,
34
  Logger logger,
35
  String header,
36
) {
37
  final total = clang.clang_getNumDiagnostics(tu);
62✔
38
  if (total == 0) {
31✔
39
    return;
40
  }
41

42
  logger.severe('Header $header: Total errors/warnings: $total.');
×
43
  for (var i = 0; i < total; i++) {
×
44
    final diag = clang.clang_getDiagnostic(tu, i);
×
45
    final cxstring = clang.clang_formatDiagnostic(
×
46
      diag,
47
      clang_types
48
              .CXDiagnosticDisplayOptions.CXDiagnostic_DisplaySourceLocation |
×
49
          clang_types.CXDiagnosticDisplayOptions.CXDiagnostic_DisplayColumn |
×
50
          clang_types
51
              .CXDiagnosticDisplayOptions.CXDiagnostic_DisplayCategoryName,
52
    );
53
    logger.severe('    ' + cxstring.toStringAndDispose());
×
54
    clang.clang_disposeDiagnostic(diag);
×
55
  }
56
}
57

58
extension CXSourceRangeExt on Pointer<clang_types.CXSourceRange> {
59
  void dispose() {
×
60
    calloc.free(this);
×
61
  }
62
}
63

64
extension CXCursorExt on clang_types.CXCursor {
65
  String usr() {
31✔
66
    return clang.clang_getCursorUSR(this).toStringAndDispose();
93✔
67
  }
68

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

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

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

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

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

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

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

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

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

121
    calloc.free(cxfilePtr);
31✔
122
    return s;
123
  }
124

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

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

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

159
const commentPrefix = '/// ';
160
const nesting = '  ';
161

162
/// Stores the [clang_types.CXSourceRange] of the last comment.
163
clang_types.CXSourceRange? lastCommentRange;
164

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

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

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

209
  final words = string.split(' ');
1✔
210

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

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

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

250
  return sb.toString().trim();
14✔
251
}
252

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

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

264
  /// Returns the typeKind int from [clang_types.CXTypeKind].
265
  int kind() {
30✔
266
    return this.kind;
30✔
267
  }
268

269
  String kindSpelling() {
30✔
270
    return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose();
120✔
271
  }
272

273
  int alignment() {
24✔
274
    return clang.clang_Type_getAlignOf(this);
48✔
275
  }
276

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

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

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

307
  void dispose() {
×
308
    clang.clang_disposeString(this);
×
309
  }
310
}
311

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

316
  for (var i = 0; i < list.length; i++) {
138✔
317
    nativeCmdArgs[i] = list[i].toNativeUtf8();
138✔
318
  }
319

320
  return nativeCmdArgs;
321
}
322

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

333
class Stack<T> {
334
  final _stack = <T>[];
335

336
  T get top => _stack.last;
84✔
337
  T pop() => _stack.removeLast();
90✔
338
  void push(T item) => _stack.add(item);
90✔
339
}
340

341
class IncrementalNamer {
342
  final _incrementedStringCounters = <String, int>{};
343

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

353
class Macro {
354
  final String usr;
355
  final String? originalName;
356

357
  Macro(this.usr, this.originalName);
9✔
358
}
359

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

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

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

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

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

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

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