Add a step for building cursor definition map. (#474)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9801108..9980c10 100644
--- a/CHANGELOG.md
+++ b/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/lib/src/header_parser/data.dart b/lib/src/header_parser/data.dart
index 1381fd3..5bd4e90 100644
--- a/lib/src/header_parser/data.dart
+++ b/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/lib/src/header_parser/parser.dart b/lib/src/header_parser/parser.dart
index 837d158..080d2f8 100644
--- a/lib/src/header_parser/parser.dart
+++ b/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/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
index 81c9761..6d98247 100644
--- a/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
+++ b/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/lib/src/header_parser/sub_parsers/enumdecl_parser.dart b/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
index b33e3e7..76cccd2 100644
--- a/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
+++ b/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/lib/src/header_parser/translation_unit_parser.dart b/lib/src/header_parser/translation_unit_parser.dart
index b34b1b1..ea9d594 100644
--- a/lib/src/header_parser/translation_unit_parser.dart
+++ b/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/lib/src/header_parser/utils.dart b/lib/src/header_parser/utils.dart
index 5f9bd33..614d5e5 100644
--- a/lib/src/header_parser/utils.dart
+++ b/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/pubspec.yaml b/pubspec.yaml
index 3ac3822..c13dfb5 100644
--- a/pubspec.yaml
+++ b/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/test/header_parser_tests/separate_definition.h b/test/header_parser_tests/separate_definition.h
new file mode 100644
index 0000000..4c8584b
--- /dev/null
+++ b/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/test/header_parser_tests/separate_definition_base.h b/test/header_parser_tests/separate_definition_base.h
new file mode 100644
index 0000000..2342bdf
--- /dev/null
+++ b/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/test/header_parser_tests/separate_definition_test.dart b/test/header_parser_tests/separate_definition_test.dart
new file mode 100644
index 0000000..6b62ade
--- /dev/null
+++ b/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;
+}