[ffigen] Add a step for building cursor definition map. (#474)
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 9801108..9980c10 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,6 @@ +# 7.1.0 +- Handle declarations with definition accessible from a different entry-point. + # 7.0.0 - Fix typedef include/exclude config.
diff --git a/pkgs/ffigen/lib/src/header_parser/data.dart b/pkgs/ffigen/lib/src/header_parser/data.dart index 1381fd3..5bd4e90 100644 --- a/pkgs/ffigen/lib/src/header_parser/data.dart +++ b/pkgs/ffigen/lib/src/header_parser/data.dart
@@ -21,6 +21,10 @@ Clang get clang => _clang; late Clang _clang; +// Cursor index. +CursorIndex get cursorIndex => _cursorIndex; +CursorIndex _cursorIndex = CursorIndex(); + // Tracks seen status for bindings BindingsIndex get bindingsIndex => _bindingsIndex; BindingsIndex _bindingsIndex = BindingsIndex(); @@ -47,6 +51,7 @@ _incrementalNamer = IncrementalNamer(); _savedMacros = {}; _unnamedEnumConstants = []; + _cursorIndex = CursorIndex(); _bindingsIndex = BindingsIndex(); _objCBuiltInFunctions = ObjCBuiltInFunctions(); }
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index 837d158..080d2f8 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -82,6 +82,9 @@ // Log all headers for user. _logger.info('Input Headers: ${config.headers.entryPoints}'); + final tuList = <Pointer<clang_types.CXTranslationUnitImpl>>[]; + + // Parse all translation units from entry points. for (final headerLocation in config.headers.entryPoints) { _logger.fine('Creating TranslationUnit for header: $headerLocation'); @@ -105,11 +108,24 @@ } logTuDiagnostics(tu, _logger, headerLocation); - final rootCursor = clang.clang_getTranslationUnitCursor(tu); + tuList.add(tu); + } + final tuCursors = + tuList.map((tu) => clang.clang_getTranslationUnitCursor(tu)); + + // Build usr to CXCusror map from translation units. + for (final rootCursor in tuCursors) { + buildUsrCursorDefinitionMap(rootCursor); + } + + // Parse definitions from translation units. + for (final rootCursor in tuCursors) { bindings.addAll(parseTranslationUnit(rootCursor)); + } - // Cleanup. + // Dispose translation units. + for (final tu in tuList) { clang.clang_disposeTranslationUnit(tu); }
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart index 81c9761..6d98247 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
@@ -106,9 +106,7 @@ } // Parse the cursor definition instead, if this is a forward declaration. - if (isForwardDeclaration(cursor)) { - cursor = clang.clang_getCursorDefinition(cursor); - } + cursor = cursorIndex.getDefinition(cursor); final declUsr = cursor.usr(); final String declName; @@ -163,6 +161,7 @@ /// generate these as opaque if `dependency-only` was set to opaque). bool pointerReference = false, }) { + cursor = cursorIndex.getDefinition(cursor); final compoundType = compound.compoundType; // Skip dependencies if already seen OR user has specified `dependency-only`
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart index b33e3e7..76cccd2 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
@@ -40,9 +40,7 @@ _stack.push(_ParsedEnum()); // Parse the cursor definition instead, if this is a forward declaration. - if (isForwardDeclaration(cursor)) { - cursor = clang.clang_getCursorDefinition(cursor); - } + cursor = cursorIndex.getDefinition(cursor); final enumUsr = cursor.usr(); final String enumName;
diff --git a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart index b34b1b1..ea9d594 100644 --- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
@@ -27,6 +27,12 @@ clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>? _rootCursorVisitorPtr; +Pointer< + NativeFunction< + Int32 Function( + clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>? + _cursorDefinitionVisitorPtr; + /// Parses the translation unit and returns the generated bindings. Set<Binding> parseTranslationUnit(clang_types.CXCursor translationUnitCursor) { _bindings = {}; @@ -94,3 +100,29 @@ final t = getCodeGenType(cursor.type(), ignoreFilter: false); return t is BindingType ? t : null; } + +/// Visits all cursors and builds a map of usr and [CXCursor]. +void buildUsrCursorDefinitionMap(clang_types.CXCursor translationUnitCursor) { + _bindings = {}; + final resultCode = clang.clang_visitChildren( + translationUnitCursor, + _cursorDefinitionVisitorPtr ??= Pointer.fromFunction( + _definitionCursorVisitor, exceptional_visitor_return), + nullptr, + ); + + visitChildrenResultChecker(resultCode); +} + +/// Child visitor invoked on translationUnitCursor [CXCursorKind.CXCursor_TranslationUnit]. +int _definitionCursorVisitor(clang_types.CXCursor cursor, + clang_types.CXCursor parent, Pointer<Void> clientData) { + try { + cursorIndex.saveDefinition(cursor); + } catch (e, s) { + _logger.severe(e); + _logger.severe(s); + rethrow; + } + return clang_types.CXChildVisitResult.CXChildVisit_Continue; +}
diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index 5f9bd33..614d5e5 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart
@@ -13,6 +13,8 @@ import 'data.dart'; import 'type_extractor/extractor.dart'; +final _logger = Logger('ffigen.header_parser.utils'); + const exceptional_visitor_return = clang_types.CXChildVisitResult.CXChildVisit_Break; @@ -242,11 +244,6 @@ return sb.toString().trim(); } -bool isForwardDeclaration(clang_types.CXCursor cursor) { - return clang.clang_Cursor_isNull(clang.clang_getCursorDefinition(cursor)) == - 0; -} - extension CXTypeExt on clang_types.CXType { /// Get code_gen [Type] representation of [clang_types.CXType]. Type toCodeGenType() { @@ -399,3 +396,43 @@ void addObjCBlockToSeen(String key, ObjCBlock t) => _objcBlocks[key] = t; ObjCBlock? getSeenObjCBlock(String key) => _objcBlocks[key]; } + +class CursorIndex { + final _usrCursorDefinition = <String, clang_types.CXCursor>{}; + + /// Returns the Cursor definition (if found) or itself. + clang_types.CXCursor getDefinition(clang_types.CXCursor cursor) { + final cursorDefinition = clang.clang_getCursorDefinition(cursor); + if (clang.clang_Cursor_isNull(cursorDefinition) == 0) { + return cursorDefinition; + } else { + final usr = cursor.usr(); + if (_usrCursorDefinition.containsKey(usr)) { + return _usrCursorDefinition[cursor.usr()]!; + } else { + _logger.warning( + "No definition found for declaration - ${cursor.completeStringRepr()}"); + return cursor; + } + } + } + + /// Saves cursor definition based on its kind. + void saveDefinition(clang_types.CXCursor cursor) { + switch (cursor.kind) { + case clang_types.CXCursorKind.CXCursor_StructDecl: + case clang_types.CXCursorKind.CXCursor_UnionDecl: + case clang_types.CXCursorKind.CXCursor_EnumDecl: + final usr = cursor.usr(); + if (!_usrCursorDefinition.containsKey(usr)) { + final cursorDefinition = clang.clang_getCursorDefinition(cursor); + if (clang.clang_Cursor_isNull(cursorDefinition) == 0) { + _usrCursorDefinition[usr] = cursorDefinition; + } else { + _logger.finest( + "Missing cursor definition in current translation unit: ${cursor.completeStringRepr()}"); + } + } + } + } +}
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index 3ac3822..c13dfb5 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml
@@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 7.0.0 +version: 7.1.0 description: Generator for FFI bindings, using LibClang to parse C header files. repository: https://github.com/dart-lang/ffigen
diff --git a/pkgs/ffigen/test/header_parser_tests/separate_definition.h b/pkgs/ffigen/test/header_parser_tests/separate_definition.h new file mode 100644 index 0000000..4c8584b --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/separate_definition.h
@@ -0,0 +1,11 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Forward declaration to SeparatelyDefinedStruct, with definition in +// separate_definition_base.h +struct SeparatelyDefinedStruct; + +void func(struct SeparatelyDefinedStruct s); + +void func2(struct SeparatelyDefinedStruct *s);
diff --git a/pkgs/ffigen/test/header_parser_tests/separate_definition_base.h b/pkgs/ffigen/test/header_parser_tests/separate_definition_base.h new file mode 100644 index 0000000..2342bdf --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/separate_definition_base.h
@@ -0,0 +1,8 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +struct SeparatelyDefinedStruct{ + int a; + int b; +};
diff --git a/pkgs/ffigen/test/header_parser_tests/separate_definition_test.dart b/pkgs/ffigen/test/header_parser_tests/separate_definition_test.dart new file mode 100644 index 0000000..6b62ade --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/separate_definition_test.dart
@@ -0,0 +1,50 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider.dart'; +import 'package:ffigen/src/header_parser.dart' as parser; +import 'package:ffigen/src/strings.dart' as strings; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; + +import '../test_utils.dart'; + +late Library actual, expected; + +void main() { + group('separate_definition', () { + setUpAll(() { + logWarnings(Level.SEVERE); + }); + test('different header order', () { + final entryPoints = [ + "test/header_parser_tests/separate_definition_base.h", + "test/header_parser_tests/separate_definition.h" + ]; + final library1String = parser.parse(_makeConfig(entryPoints)).generate(); + final library2String = + parser.parse(_makeConfig(entryPoints.reversed.toList())).generate(); + + expect(library1String, library2String); + }); + }); +} + +Config _makeConfig(List<String> entryPoints) { + final entryPointBuilder = StringBuffer(); + for (final ep in entryPoints) { + entryPointBuilder.writeln(" - $ep"); + } + final config = Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'Bindings' +${strings.output}: 'unused' + +${strings.headers}: + ${strings.entryPoints}: +${entryPointBuilder.toString()} +''') as yaml.YamlMap); + return config; +}