[ffigen] Added support for generating Unions (#215)
diff --git a/pkgs/ffigen/.github/workflows/test-package.yml b/pkgs/ffigen/.github/workflows/test-package.yml index da0cc9b..7cc704e 100644 --- a/pkgs/ffigen/.github/workflows/test-package.yml +++ b/pkgs/ffigen/.github/workflows/test-package.yml
@@ -19,7 +19,7 @@ strategy: fail-fast: false matrix: - sdk: [2.13.0] + sdk: [2.14.0-115.0.dev] # TODO: revert to 2.14 steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v1.0 @@ -43,7 +43,7 @@ - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v1.0 with: - sdk: 2.13.0 + sdk: 2.14.0-115.0.dev # TODO: revert to 2.14. - name: Install dependencies run: dart pub get - name: Install libclang-10-dev
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 3b1f928..6950615 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,6 @@ +# 3.1.0-dev.0 +- Added support for generating unions. + # 3.0.0 - Release for dart sdk `>=2.13` (Support for packed structs and inline arrays).
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index 099fe04..ae66d69 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -181,7 +181,7 @@ </td> </tr> <tr> - <td>functions<br><br>structs<br><br>enums<br><br>unnamed-enums<br><br>macros<br><br>globals</td> + <td>functions<br><br>structs<br><br>unions<br><br>enums<br><br>unnamed-enums<br><br>macros<br><br>globals</td> <td>Filters for declarations.<br><b>Default: all are included.</b><br><br> Options -<br> - Include/Exclude declarations.<br> @@ -273,8 +273,10 @@ </td> </tr> <tr> - <td>structs -> dependency-only</td> - <td>If `opaque`, generates empty `Opaque` structs if structs + <td>structs -> dependency-only<br><br> + unions -> dependency-only + </td> + <td>If `opaque`, generates empty `Opaque` structs/unions if they were not included in config (but were added since they are a dependency) and only passed by reference(pointer).<br> <i>Options - full(default) | opaque</i><br> @@ -284,6 +286,8 @@ ```yaml structs: dependency-only: opaque +unions: + dependency-only: opaque ``` </td> </tr> @@ -476,9 +480,9 @@ 'CXType_(.*)': '$1' ``` -### Why are some struct declarations generated even after excluded them in config? +### Why are some struct/union declarations generated even after excluded them in config? -This happens when an excluded struct is a dependency to some included declaration. +This happens when an excluded struct/union is a dependency to some included declaration. (A dependency means a struct is being passed/returned by a function or is member of another struct in some way) Note: If you supply `structs` -> `dependency-only` as `opaque` ffigen will generate @@ -486,6 +490,8 @@ ```yaml structs: dependency-only: opaque +unions: + dependency-only: opaque ``` ### How to expose the native pointers and typedefs?
diff --git a/pkgs/ffigen/lib/src/code_generator.dart b/pkgs/ffigen/lib/src/code_generator.dart index efbb1a2..0b23674 100644 --- a/pkgs/ffigen/lib/src/code_generator.dart +++ b/pkgs/ffigen/lib/src/code_generator.dart
@@ -6,6 +6,7 @@ library code_generator; export 'code_generator/binding.dart'; +export 'code_generator/compound.dart'; export 'code_generator/constant.dart'; export 'code_generator/enum_class.dart'; export 'code_generator/func.dart'; @@ -14,3 +15,4 @@ export 'code_generator/struc.dart'; export 'code_generator/type.dart'; export 'code_generator/typedef.dart'; +export 'code_generator/union.dart';
diff --git a/pkgs/ffigen/lib/src/code_generator/binding_string.dart b/pkgs/ffigen/lib/src/code_generator/binding_string.dart index a2b721e..2929ee8 100644 --- a/pkgs/ffigen/lib/src/code_generator/binding_string.dart +++ b/pkgs/ffigen/lib/src/code_generator/binding_string.dart
@@ -18,6 +18,7 @@ enum BindingStringType { func, struc, + union, constant, global, enumClass,
diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart new file mode 100644 index 0000000..77868eb --- /dev/null +++ b/pkgs/ffigen/lib/src/code_generator/compound.dart
@@ -0,0 +1,184 @@ +// Copyright (c) 2021, 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/code_generator/typedef.dart'; + +import 'binding.dart'; +import 'binding_string.dart'; +import 'type.dart'; +import 'utils.dart'; +import 'writer.dart'; + +enum CompoundType { struct, union } + +/// A binding for Compound type - Struct/Union. +abstract class Compound extends NoLookUpBinding { + /// Marker for if a struct definition is complete. + /// + /// A function can be safely pass this struct by value if it's complete. + bool isInComplete; + + List<Member> members; + + bool get isOpaque => members.isEmpty; + + /// Value for `@Packed(X)` annotation. Can be null(no packing), 1, 2, 4, 8, 16. + /// + /// Only supported for [CompoundType.struct]. + int? pack; + + /// Marker for checking if the dependencies are parsed. + bool parsedDependencies = false; + + CompoundType compoundType; + bool get isStruct => compoundType == CompoundType.struct; + bool get isUnion => compoundType == CompoundType.union; + + Compound({ + String? usr, + String? originalName, + required String name, + required this.compoundType, + this.isInComplete = false, + this.pack, + String? dartDoc, + List<Member>? members, + }) : members = members ?? [], + super( + usr: usr, + originalName: originalName, + name: name, + dartDoc: dartDoc, + ); + + factory Compound.fromType({ + required CompoundType type, + String? usr, + String? originalName, + required String name, + bool isInComplete = false, + int? pack, + String? dartDoc, + List<Member>? members, + }) { + switch (type) { + case CompoundType.struct: + return Struc( + usr: usr, + originalName: originalName, + name: name, + isInComplete: isInComplete, + pack: pack, + dartDoc: dartDoc, + members: members, + ); + case CompoundType.union: + return Union( + usr: usr, + originalName: originalName, + name: name, + isInComplete: isInComplete, + pack: pack, + dartDoc: dartDoc, + members: members, + ); + } + } + + List<int> _getArrayDimensionLengths(Type type) { + final array = <int>[]; + var startType = type; + while (startType.broadType == BroadType.ConstantArray) { + array.add(startType.length!); + startType = startType.child!; + } + return array; + } + + String _getInlineArrayTypeString(Type type, Writer w) { + if (type.broadType == BroadType.ConstantArray) { + return '${w.ffiLibraryPrefix}.Array<${_getInlineArrayTypeString(type.child!, w)}>'; + } + return type.getCType(w); + } + + List<Typedef>? _typedefDependencies; + @override + List<Typedef> getTypedefDependencies(Writer w) { + if (_typedefDependencies == null) { + _typedefDependencies = <Typedef>[]; + + // Write typedef's required by members and resolve name conflicts. + for (final m in members) { + final base = m.type.getBaseType(); + if (base.broadType == BroadType.NativeFunction) { + _typedefDependencies!.addAll(base.nativeFunc!.getDependencies()); + } + } + } + return _typedefDependencies ?? []; + } + + @override + BindingString toBindingString(Writer w) { + final s = StringBuffer(); + final enclosingClassName = name; + if (dartDoc != null) { + s.write(makeDartDoc(dartDoc!)); + } + + /// Adding [enclosingClassName] because dart doesn't allow class member + /// to have the same name as the class. + final localUniqueNamer = UniqueNamer({enclosingClassName}); + + /// Write @Packed(X) annotation if struct is packed. + if (isStruct && pack != null) { + s.write('@${w.ffiLibraryPrefix}.Packed($pack)\n'); + } + final dartClassName = isStruct ? 'Struct' : 'Union'; + // Write class declaration. + s.write( + 'class $enclosingClassName extends ${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : dartClassName}{\n'); + const depth = ' '; + for (final m in members) { + final memberName = localUniqueNamer.makeUnique(m.name); + if (m.type.broadType == BroadType.ConstantArray) { + s.write( + '$depth@${w.ffiLibraryPrefix}.Array.multi(${_getArrayDimensionLengths(m.type)})\n'); + s.write( + '${depth}external ${_getInlineArrayTypeString(m.type, w)} $memberName;\n\n'); + } else { + if (m.dartDoc != null) { + s.write(depth + '/// '); + s.writeAll(m.dartDoc!.split('\n'), '\n' + depth + '/// '); + s.write('\n'); + } + if (m.type.isPrimitive) { + s.write('$depth@${m.type.getCType(w)}()\n'); + } + s.write('${depth}external ${m.type.getDartType(w)} $memberName;\n\n'); + } + } + s.write('}\n\n'); + + return BindingString( + type: isStruct ? BindingStringType.struc : BindingStringType.union, + string: s.toString()); + } +} + +class Member { + final String? dartDoc; + final String originalName; + final String name; + final Type type; + + const Member({ + String? originalName, + required this.name, + required this.type, + this.dartDoc, + }) : originalName = originalName ?? name; +}
diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart index 1951371..cf864cd 100644 --- a/pkgs/ffigen/lib/src/code_generator/global.dart +++ b/pkgs/ffigen/lib/src/code_generator/global.dart
@@ -66,8 +66,8 @@ s.write( "late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n"); - if (type.broadType == BroadType.Struct) { - if (type.struc!.isOpaque) { + if (type.broadType == BroadType.Compound) { + if (type.compound!.isOpaque) { s.write( '${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName => $pointerName;\n\n'); } else {
diff --git a/pkgs/ffigen/lib/src/code_generator/struc.dart b/pkgs/ffigen/lib/src/code_generator/struc.dart index 8b87891..4f06369 100644 --- a/pkgs/ffigen/lib/src/code_generator/struc.dart +++ b/pkgs/ffigen/lib/src/code_generator/struc.dart
@@ -2,13 +2,7 @@ // 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/typedef.dart'; - -import 'binding.dart'; -import 'binding_string.dart'; -import 'type.dart'; -import 'utils.dart'; -import 'writer.dart'; +import 'package:ffigen/src/code_generator/compound.dart'; /// A binding for C Struct. /// @@ -34,127 +28,23 @@ /// /// } /// ``` -class Struc extends NoLookUpBinding { - /// Marker for if a struct definition is complete. - /// - /// A function can be safely pass this struct by value if it's complete. - bool isInComplete; - - List<Member> members; - - bool get isOpaque => members.isEmpty; - - /// Value for `@Packed(X)` annotation. Can be null(no packing), 1, 2, 4, 8, 16. - int? pack; - - /// Marker for checking if the dependencies are parsed. - bool parsedDependencies = false; - +class Struc extends Compound { Struc({ String? usr, String? originalName, required String name, - this.isInComplete = false, - this.pack, + bool isInComplete = false, + int? pack, String? dartDoc, List<Member>? members, - }) : members = members ?? [], - super( + }) : super( usr: usr, originalName: originalName, name: name, dartDoc: dartDoc, + isInComplete: isInComplete, + members: members, + pack: pack, + compoundType: CompoundType.struct, ); - - List<int> _getArrayDimensionLengths(Type type) { - final array = <int>[]; - var startType = type; - while (startType.broadType == BroadType.ConstantArray) { - array.add(startType.length!); - startType = startType.child!; - } - return array; - } - - String _getInlineArrayTypeString(Type type, Writer w) { - if (type.broadType == BroadType.ConstantArray) { - return '${w.ffiLibraryPrefix}.Array<${_getInlineArrayTypeString(type.child!, w)}>'; - } - return type.getCType(w); - } - - List<Typedef>? _typedefDependencies; - @override - List<Typedef> getTypedefDependencies(Writer w) { - if (_typedefDependencies == null) { - _typedefDependencies = <Typedef>[]; - - // Write typedef's required by members and resolve name conflicts. - for (final m in members) { - final base = m.type.getBaseType(); - if (base.broadType == BroadType.NativeFunction) { - _typedefDependencies!.addAll(base.nativeFunc!.getDependencies()); - } - } - } - return _typedefDependencies ?? []; - } - - @override - BindingString toBindingString(Writer w) { - final s = StringBuffer(); - final enclosingClassName = name; - if (dartDoc != null) { - s.write(makeDartDoc(dartDoc!)); - } - - /// Adding [enclosingClassName] because dart doesn't allow class member - /// to have the same name as the class. - final localUniqueNamer = UniqueNamer({enclosingClassName}); - - /// Write @Packed(X) annotation if struct is packed. - if (pack != null) { - s.write('@${w.ffiLibraryPrefix}.Packed($pack)\n'); - } - // Write class declaration. - s.write( - 'class $enclosingClassName extends ${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : 'Struct'}{\n'); - const depth = ' '; - for (final m in members) { - final memberName = localUniqueNamer.makeUnique(m.name); - if (m.type.broadType == BroadType.ConstantArray) { - s.write( - '$depth@${w.ffiLibraryPrefix}.Array.multi(${_getArrayDimensionLengths(m.type)})\n'); - s.write( - '${depth}external ${_getInlineArrayTypeString(m.type, w)} $memberName;\n\n'); - } else { - if (m.dartDoc != null) { - s.write(depth + '/// '); - s.writeAll(m.dartDoc!.split('\n'), '\n' + depth + '/// '); - s.write('\n'); - } - if (m.type.isPrimitive) { - s.write('$depth@${m.type.getCType(w)}()\n'); - } - s.write('${depth}external ${m.type.getDartType(w)} $memberName;\n\n'); - } - } - s.write('}\n\n'); - - return BindingString(type: BindingStringType.struc, string: s.toString()); - } -} - -class Member { - final String? dartDoc; - final String originalName; - final String name; - final Type type; - - const Member({ - String? originalName, - required this.name, - required this.type, - this.dartDoc, - }) : originalName = originalName ?? name; }
diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart index 74b3aa5..c306395 100644 --- a/pkgs/ffigen/lib/src/code_generator/type.dart +++ b/pkgs/ffigen/lib/src/code_generator/type.dart
@@ -2,7 +2,9 @@ // 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 'struc.dart'; +import 'package:ffigen/src/code_generator.dart'; + +import 'compound.dart'; import 'typedef.dart'; import 'writer.dart'; @@ -34,7 +36,7 @@ Boolean, NativeType, Pointer, - Struct, + Compound, NativeFunction, /// Represents a Dart_Handle. @@ -44,7 +46,7 @@ ConstantArray, IncompleteArray, - /// Used as a marker, so that functions/structs having these can exclude them. + /// Used as a marker, so that declarations having these can exclude them. Unimplemented, } @@ -66,8 +68,8 @@ SupportedNativeType.IntPtr: _SubType(c: 'IntPtr', dart: 'int'), }; - /// Reference to the [Struc] binding this type refers to. - Struc? struc; + /// Reference to the [Compound] binding this type refers to. + Compound? compound; /// Reference to the [Typedef] this type refers to. Typedef? nativeFunc; @@ -90,7 +92,7 @@ Type._({ required this.broadType, this.child, - this.struc, + this.compound, this.nativeType, this.nativeFunc, this.length, @@ -100,8 +102,14 @@ factory Type.pointer(Type child) { return Type._(broadType: BroadType.Pointer, child: child); } + factory Type.compound(Compound compound) { + return Type._(broadType: BroadType.Compound, compound: compound); + } factory Type.struct(Struc struc) { - return Type._(broadType: BroadType.Struct, struc: struc); + return Type._(broadType: BroadType.Compound, compound: struc); + } + factory Type.union(Union union) { + return Type._(broadType: BroadType.Compound, compound: union); } factory Type.nativeFunc(Typedef nativeFunc) { return Type._(broadType: BroadType.NativeFunction, nativeFunc: nativeFunc); @@ -162,11 +170,13 @@ bool get isPrimitive => (broadType == BroadType.NativeType || broadType == BroadType.Boolean); - /// Returns true if the type is a [Struc] and is incomplete. - bool get isIncompleteStruct => - (broadType == BroadType.Struct && struc != null && struc!.isInComplete) || + /// Returns true if the type is a [Compound] and is incomplete. + bool get isIncompleteCompound => + (broadType == BroadType.Compound && + compound != null && + compound!.isInComplete) || (broadType == BroadType.ConstantArray && - getBaseArrayType().isIncompleteStruct); + getBaseArrayType().isIncompleteCompound); String getCType(Writer w) { switch (broadType) { @@ -174,8 +184,8 @@ return '${w.ffiLibraryPrefix}.${_primitives[nativeType!]!.c}'; case BroadType.Pointer: return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>'; - case BroadType.Struct: - return '${struc!.name}'; + case BroadType.Compound: + return '${compound!.name}'; case BroadType.NativeFunction: return '${w.ffiLibraryPrefix}.NativeFunction<${nativeFunc!.name}>'; case BroadType @@ -199,8 +209,8 @@ return _primitives[nativeType!]!.dart; case BroadType.Pointer: return '${w.ffiLibraryPrefix}.Pointer<${child!.getCType(w)}>'; - case BroadType.Struct: - return '${struc!.name}'; + case BroadType.Compound: + return '${compound!.name}'; case BroadType.NativeFunction: return '${w.ffiLibraryPrefix}.NativeFunction<${nativeFunc!.name}>'; case BroadType
diff --git a/pkgs/ffigen/lib/src/code_generator/union.dart b/pkgs/ffigen/lib/src/code_generator/union.dart new file mode 100644 index 0000000..a8a0861 --- /dev/null +++ b/pkgs/ffigen/lib/src/code_generator/union.dart
@@ -0,0 +1,49 @@ +// 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/compound.dart'; + +/// A binding for a C union - +/// +/// ```c +/// union C { +/// int a; +/// double b; +/// float c; +/// }; +/// ``` +/// The generated dart code is - +/// ```dart +/// class Union extends ffi.Union{ +/// @ffi.Int32() +/// int a; +/// +/// @ffi.Double() +/// double b; +/// +/// @ffi.Float() +/// float c; +/// +/// } +/// ``` +class Union extends Compound { + Union({ + String? usr, + String? originalName, + required String name, + bool isInComplete = false, + int? pack, + String? dartDoc, + List<Member>? members, + }) : super( + usr: usr, + originalName: originalName, + name: name, + dartDoc: dartDoc, + isInComplete: isInComplete, + members: members, + pack: pack, + compoundType: CompoundType.union, + ); +}
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index e42ed46..92194dc 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -43,6 +43,10 @@ Declaration get structDecl => _structDecl; late Declaration _structDecl; + /// Declaration config for Unions. + Declaration get unionDecl => _unionDecl; + late Declaration _unionDecl; + /// Declaration config for Enums. Declaration get enumClassDecl => _enumClassDecl; late Declaration _enumClassDecl; @@ -77,8 +81,12 @@ late CommentType _commentType; /// Whether structs that are dependencies should be included. - StructDependencies get structDependencies => _structDependencies; - late StructDependencies _structDependencies; + CompoundDependencies get structDependencies => _structDependencies; + late CompoundDependencies _structDependencies; + + /// Whether unions that are dependencies should be included. + CompoundDependencies get unionDependencies => _unionDependencies; + late CompoundDependencies _unionDependencies; /// Holds config for how struct packing should be overriden. StructPackingOverride get structPackingOverride => _structPackingOverride; @@ -234,6 +242,15 @@ _structDecl = result as Declaration; }, ), + [strings.unions]: Specification<Declaration>( + requirement: Requirement.no, + validator: declarationConfigValidator, + extractor: declarationConfigExtractor, + defaultValue: () => Declaration(), + extractedResult: (dynamic result) { + _unionDecl = result as Declaration; + }, + ), [strings.enums]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, @@ -312,14 +329,23 @@ extractedResult: (dynamic result) => _commentType = result as CommentType, ), - [strings.structs, strings.structDependencies]: - Specification<StructDependencies>( + [strings.structs, strings.dependencyOnly]: + Specification<CompoundDependencies>( requirement: Requirement.no, - validator: structDependenciesValidator, - extractor: structDependenciesExtractor, - defaultValue: () => StructDependencies.full, + validator: dependencyOnlyValidator, + extractor: dependencyOnlyExtractor, + defaultValue: () => CompoundDependencies.full, extractedResult: (dynamic result) => - _structDependencies = result as StructDependencies, + _structDependencies = result as CompoundDependencies, + ), + [strings.unions, strings.dependencyOnly]: + Specification<CompoundDependencies>( + requirement: Requirement.no, + validator: dependencyOnlyValidator, + extractor: dependencyOnlyExtractor, + defaultValue: () => CompoundDependencies.full, + extractedResult: (dynamic result) => + _unionDependencies = result as CompoundDependencies, ), [strings.structs, strings.structPack]: Specification<StructPackingOverride>(
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index 7fcc352..42e99f0 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -29,7 +29,7 @@ enum CommentStyle { doxygen, any } enum CommentLength { none, brief, full } -enum StructDependencies { full, opaque } +enum CompoundDependencies { full, opaque } /// Holds config for how Structs Packing will be overriden. class StructPackingOverride {
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index 73c0d0d..a36ee50 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -658,21 +658,21 @@ } } -StructDependencies structDependenciesExtractor(dynamic value) { - var result = StructDependencies.full; - if (value == strings.opaqueStructDependencies) { - result = StructDependencies.opaque; +CompoundDependencies dependencyOnlyExtractor(dynamic value) { + var result = CompoundDependencies.full; + if (value == strings.opaqueCompoundDependencies) { + result = CompoundDependencies.opaque; } return result; } -bool structDependenciesValidator(List<String> name, dynamic value) { +bool dependencyOnlyValidator(List<String> name, dynamic value) { var result = true; if (value is! String || - !(value == strings.fullStructDependencies || - value == strings.opaqueStructDependencies)) { + !(value == strings.fullCompoundDependencies || + value == strings.opaqueCompoundDependencies)) { _logger.severe( - "'$name' must be one of the following - {${strings.fullStructDependencies}, ${strings.opaqueStructDependencies}}"); + "'$name' must be one of the following - {${strings.fullCompoundDependencies}, ${strings.opaqueCompoundDependencies}}"); result = false; } return result;
diff --git a/pkgs/ffigen/lib/src/header_parser/includer.dart b/pkgs/ffigen/lib/src/header_parser/includer.dart index bd82476..1cd4ff1 100644 --- a/pkgs/ffigen/lib/src/header_parser/includer.dart +++ b/pkgs/ffigen/lib/src/header_parser/includer.dart
@@ -17,6 +17,16 @@ } } +bool shouldIncludeUnion(String usr, String name) { + if (bindingsIndex.isSeenStruct(usr) || name == '') { + return false; + } else if (config.unionDecl.shouldInclude(name)) { + return true; + } else { + return false; + } +} + bool shouldIncludeFunc(String usr, String name) { if (bindingsIndex.isSeenFunc(usr) || name == '') { return false;
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 new file mode 100644 index 0000000..82bac38 --- /dev/null +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
@@ -0,0 +1,303 @@ +// Copyright (c) 2021, 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 'dart:ffi'; + +import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; +import 'package:logging/logging.dart'; + +import '../../strings.dart' as strings; +import '../clang_bindings/clang_bindings.dart' as clang_types; +import '../data.dart'; +import '../includer.dart'; +import '../utils.dart'; + +final _logger = Logger('ffigen.header_parser.compounddecl_parser'); + +/// Holds temporary information regarding [compound] while parsing. +class _ParsedCompound { + Compound? compound; + bool unimplementedMemberType = false; + bool flexibleArrayMember = false; + bool bitFieldMember = false; + bool dartHandleMember = false; + bool incompleteCompoundMember = false; + + bool get isInComplete => + unimplementedMemberType || + flexibleArrayMember || + bitFieldMember || + (dartHandleMember && config.useDartHandle) || + incompleteCompoundMember; + + // A struct without any attribute is definitely not packed. #pragma pack(...) + // also adds an attribute, but it's unexposed and cannot be travesed. + bool hasAttr = false; + // A struct which as a __packed__ attribute is definitely packed. + bool hasPackedAttr = false; + // Stores the maximum alignment from all the children. + int maxChildAlignment = 0; + // Alignment of this struct. + int allignment = 0; + + bool get _isPacked { + if (!hasAttr || isInComplete) return false; + if (hasPackedAttr) return true; + + return maxChildAlignment > allignment; + } + + /// Returns pack value of a struct depending on config, returns null for no + /// packing. + int? get packValue { + if (compound!.isStruct && _isPacked) { + if (strings.packingValuesMap.containsKey(allignment)) { + return allignment; + } else { + _logger.warning( + 'Unsupported pack value "$allignment" for Struct "${compound!.name}".'); + return null; + } + } else { + return null; + } + } + + _ParsedCompound(); +} + +final _stack = Stack<_ParsedCompound>(); + +/// Parses a compound declaration. +Compound? parseCompoundDeclaration( + clang_types.CXCursor cursor, + CompoundType compoundType, { + + /// Optionally provide name (useful in case declaration is inside a typedef). + String? name, + + /// Option to ignore declaration filter (Useful in case of extracting + /// declarations when they are passed/returned by an included function.) + bool ignoreFilter = false, + + /// To track if the declaration was used by reference(i.e T*). (Used to only + /// generate these as opaque if `dependency-only` was set to opaque). + bool pointerReference = false, + + /// If the compound name should be updated, if it was already seen. + bool updateName = true, +}) { + _stack.push(_ParsedCompound()); + + // Parse the cursor definition instead, if this is a forward declaration. + if (isForwardDeclaration(cursor)) { + cursor = clang.clang_getCursorDefinition(cursor); + } + final declUsr = cursor.usr(); + final declName = name ?? cursor.spelling(); + + // Set includer functions according to compoundType. + final bool Function(String, String) shouldIncludeDecl; + final bool Function(String) isSeenDecl; + final Compound? Function(String) getSeenDecl; + final void Function(String, Compound) addDeclToSeen; + final Declaration configDecl; + final String className; + switch (compoundType) { + case CompoundType.struct: + shouldIncludeDecl = shouldIncludeStruct; + isSeenDecl = bindingsIndex.isSeenStruct; + getSeenDecl = bindingsIndex.getSeenStruct; + addDeclToSeen = bindingsIndex.addStructToSeen; + configDecl = config.structDecl; + className = 'Struct'; + break; + case CompoundType.union: + shouldIncludeDecl = shouldIncludeUnion; + isSeenDecl = bindingsIndex.isSeenUnion; + getSeenDecl = bindingsIndex.getSeenUnion; + addDeclToSeen = bindingsIndex.addUnionToSeen; + configDecl = config.unionDecl; + className = 'Union'; + break; + } + + if (declName.isEmpty) { + if (ignoreFilter) { + // This declaration is defined inside some other declaration and hence + // must be generated. + _stack.top.compound = Compound.fromType( + type: compoundType, + name: incrementalNamer.name('unnamed$className'), + usr: declUsr, + dartDoc: getCursorDocComment(cursor), + ); + _setMembers(cursor, className); + } else { + _logger.finest('unnamed $className or typedef $className declaration'); + } + } else { + if ((ignoreFilter || shouldIncludeDecl(declUsr, declName)) && + (!isSeenDecl(declUsr))) { + _logger.fine( + '++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}'); + _stack.top.compound = Compound.fromType( + type: compoundType, + usr: declUsr, + originalName: declName, + name: configDecl.renameUsingConfig(declName), + dartDoc: getCursorDocComment(cursor), + ); + // Adding to seen here to stop recursion if a declaration has itself as a + // member, members are updated later. + addDeclToSeen(declUsr, _stack.top.compound!); + } + } + + if (isSeenDecl(declUsr)) { + _stack.top.compound = getSeenDecl(declUsr); + + // Skip dependencies if already seen OR user has specified `dependency-only` + // as opaque AND this is a pointer reference AND the declaration was not + // included according to config (ignoreFilter). + final skipDependencies = _stack.top.compound!.parsedDependencies || + (pointerReference && + ignoreFilter && + ((compoundType == CompoundType.struct && + config.structDependencies == CompoundDependencies.opaque) || + (compoundType == CompoundType.union && + config.unionDependencies == CompoundDependencies.opaque))); + + if (!skipDependencies) { + // Prevents infinite recursion if struct has a pointer to itself. + _stack.top.compound!.parsedDependencies = true; + _setMembers(cursor, className); + } else if (!_stack.top.compound!.parsedDependencies) { + _logger.fine('Skipped dependencies.'); + } + + if (updateName) { + // If struct is seen, update it's name. + _stack.top.compound!.name = configDecl.renameUsingConfig(declName); + } + } + + return _stack.pop().compound; +} + +void _setMembers(clang_types.CXCursor cursor, String className) { + _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0; + _stack.top.allignment = cursor.type().alignment(); + + final resultCode = clang.clang_visitChildren( + cursor, + Pointer.fromFunction(_compoundMembersVisitor, + clang_types.CXChildVisitResult.CXChildVisit_Break), + nullptr, + ); + + _logger.finest( + 'Opaque: ${_stack.top.isInComplete}, HasAttr: ${_stack.top.hasAttr}, AlignValue: ${_stack.top.allignment}, MaxChildAlignValue: ${_stack.top.maxChildAlignment}, PackValue: ${_stack.top.packValue}.'); + _stack.top.compound!.pack = _stack.top.packValue; + + visitChildrenResultChecker(resultCode); + + if (_stack.top.unimplementedMemberType) { + _logger.fine( + '---- Removed $className members, reason: member with unimplementedtype ${cursor.completeStringRepr()}'); + _logger.warning( + 'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), struct member has an unsupported type.'); + } else if (_stack.top.flexibleArrayMember) { + _logger.fine( + '---- Removed $className members, reason: incomplete array member ${cursor.completeStringRepr()}'); + _logger.warning( + 'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Flexible array members not supported.'); + } else if (_stack.top.bitFieldMember) { + _logger.fine( + '---- Removed $className members, reason: bitfield members ${cursor.completeStringRepr()}'); + _logger.warning( + 'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Bit Field members not supported.'); + } else if (_stack.top.dartHandleMember && config.useDartHandle) { + _logger.fine( + '---- Removed $className members, reason: Dart_Handle member. ${cursor.completeStringRepr()}'); + _logger.warning( + 'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Dart_Handle member not supported.'); + } else if (_stack.top.incompleteCompoundMember) { + _logger.fine( + '---- Removed $className members, reason: Incomplete Nested Struct member. ${cursor.completeStringRepr()}'); + _logger.warning( + 'Removed All $className Members from ${_stack.top.compound!.name}(${_stack.top.compound!.originalName}), Incomplete Nested Struct member not supported.'); + } + + // Clear all members if declaration is incomplete. + if (_stack.top.isInComplete) { + _stack.top.compound!.members.clear(); + } + + // C allows empty structs/union, but it's undefined behaviour at runtine. + // So we need to mark a declaration incomplete if it has no members. + _stack.top.compound!.isInComplete = + _stack.top.isInComplete || _stack.top.compound!.members.isEmpty; +} + +/// Visitor for the struct/union cursor [CXCursorKind.CXCursor_StructDecl]/ +/// [CXCursorKind.CXCursor_UnionDecl]. +/// +/// Child visitor invoked on struct/union cursor. +int _compoundMembersVisitor(clang_types.CXCursor cursor, + clang_types.CXCursor parent, Pointer<Void> clientData) { + try { + if (cursor.kind == clang_types.CXCursorKind.CXCursor_FieldDecl) { + _logger.finer('===== member: ${cursor.completeStringRepr()}'); + + // Set maxChildAlignValue. + final align = cursor.type().alignment(); + if (align > _stack.top.maxChildAlignment) { + _stack.top.maxChildAlignment = align; + } + + final mt = cursor.type().toCodeGenType(); + if (mt.broadType == BroadType.IncompleteArray) { + // TODO(68): Structs with flexible Array Members are not supported. + _stack.top.flexibleArrayMember = true; + } + if (clang.clang_getFieldDeclBitWidth(cursor) != -1) { + // TODO(84): Struct with bitfields are not suppoorted. + _stack.top.bitFieldMember = true; + } + if (mt.broadType == BroadType.Handle) { + _stack.top.dartHandleMember = true; + } + if (mt.isIncompleteCompound) { + _stack.top.incompleteCompoundMember = true; + } + if (mt.getBaseType().broadType == BroadType.Unimplemented) { + _stack.top.unimplementedMemberType = true; + } + + _stack.top.compound!.members.add( + Member( + dartDoc: getCursorDocComment( + cursor, + nesting.length + commentPrefix.length, + ), + originalName: cursor.spelling(), + name: config.structDecl.renameMemberUsingConfig( + _stack.top.compound!.originalName, + cursor.spelling(), + ), + type: mt, + ), + ); + } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) { + _stack.top.hasPackedAttr = true; + } + } 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/sub_parsers/functiondecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart index ac1d11d..a715f0e 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -45,7 +45,7 @@ .func; // Returning null so that [addToBindings] function excludes this. } - if (rt.isIncompleteStruct || _stack.top.incompleteStructParameter) { + if (rt.isIncompleteCompound || _stack.top.incompleteStructParameter) { _logger.fine( '---- Removed Function, reason: Incomplete struct pass/return by value: ${cursor.completeStringRepr()}'); _logger.warning( @@ -101,7 +101,7 @@ _logger.finer('===== parameter: ${paramCursor.completeStringRepr()}'); final pt = _getParameterType(paramCursor); - if (pt.isIncompleteStruct) { + if (pt.isIncompleteCompound) { _stack.top.incompleteStructParameter = true; } else if (pt.getBaseType().broadType == BroadType.Unimplemented) { _logger
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart deleted file mode 100644 index e5970f4..0000000 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart +++ /dev/null
@@ -1,264 +0,0 @@ -// 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 'dart:ffi'; - -import 'package:ffigen/src/code_generator.dart'; -import 'package:ffigen/src/config_provider/config_types.dart'; -import 'package:logging/logging.dart'; - -import '../../strings.dart' as strings; -import '../clang_bindings/clang_bindings.dart' as clang_types; -import '../data.dart'; -import '../includer.dart'; -import '../utils.dart'; - -final _logger = Logger('ffigen.header_parser.structdecl_parser'); - -/// Holds temporary information regarding [struc] while parsing. -class _ParsedStruc { - Struc? struc; - bool unimplementedMemberType = false; - bool flexibleArrayMember = false; - bool bitFieldMember = false; - bool dartHandleMember = false; - bool incompleteStructMember = false; - - bool get isInComplete => - unimplementedMemberType || - flexibleArrayMember || - bitFieldMember || - (dartHandleMember && config.useDartHandle) || - incompleteStructMember; - - // A struct without any attribute is definitely not packed. #pragma pack(...) - // also adds an attribute, but it's unexposed and cannot be travesed. - bool hasAttr = false; - // A struct which as a __packed__ attribute is definitely packed. - bool hasPackedAttr = false; - // Stores the maximum alignment from all the children. - int maxChildAlignment = 0; - // Alignment of this struct. - int allignment = 0; - - bool get _isPacked { - if (!hasAttr || isInComplete) return false; - if (hasPackedAttr) return true; - - return maxChildAlignment > allignment; - } - - /// Returns pack value of a struct depending on config, returns null for no - /// packing. - int? get packValue { - if (_isPacked) { - if (strings.packingValuesMap.containsKey(allignment)) { - return allignment; - } else { - _logger.warning( - 'Unsupported pack value "$allignment" for Struct "${struc!.name}".'); - return null; - } - } else { - return null; - } - } - - _ParsedStruc(); -} - -final _stack = Stack<_ParsedStruc>(); - -/// Parses a struct declaration. -Struc? parseStructDeclaration( - clang_types.CXCursor cursor, { - - /// Optionally provide name (useful in case struct is inside a typedef). - String? name, - - /// Option to ignore struct filter (Useful in case of extracting structs - /// when they are passed/returned by an included function.) - bool ignoreFilter = false, - - /// To track if the struct was used by reference(i.e struct*). (Used to only - /// generate these as opaque if `struct-dependencies` was set to opaque). - bool pointerReference = false, - - /// If the struct name should be updated, if it was already seen. - bool updateName = true, -}) { - _stack.push(_ParsedStruc()); - - // Parse the cursor definition instead, if this is a forward declaration. - if (isForwardDeclaration(cursor)) { - cursor = clang.clang_getCursorDefinition(cursor); - } - final structUsr = cursor.usr(); - final structName = name ?? cursor.spelling(); - - if (structName.isEmpty) { - if (ignoreFilter) { - // This struct is defined inside some other struct and hence must be generated. - _stack.top.struc = Struc( - name: incrementalNamer.name('unnamedStruct'), - usr: structUsr, - dartDoc: getCursorDocComment(cursor), - ); - _setStructMembers(cursor); - } else { - _logger.finest('unnamed structure or typedef structure declaration'); - } - } else if ((ignoreFilter || shouldIncludeStruct(structUsr, structName)) && - (!bindingsIndex.isSeenStruct(structUsr))) { - _logger.fine( - '++++ Adding Structure: structName: $structName, ${cursor.completeStringRepr()}'); - _stack.top.struc = Struc( - usr: structUsr, - originalName: structName, - name: config.structDecl.renameUsingConfig(structName), - dartDoc: getCursorDocComment(cursor), - ); - // Adding to seen here to stop recursion if a struct has itself as a - // member, members are updated later. - bindingsIndex.addStructToSeen(structUsr, _stack.top.struc!); - } - - if (bindingsIndex.isSeenStruct(structUsr)) { - _stack.top.struc = bindingsIndex.getSeenStruct(structUsr); - - final skipDependencies = _stack.top.struc!.parsedDependencies || - (config.structDependencies == StructDependencies.opaque && - pointerReference && - ignoreFilter); - - if (!skipDependencies) { - // Prevents infinite recursion if struct has a pointer to itself. - _stack.top.struc!.parsedDependencies = true; - _setStructMembers(cursor); - } else if (!_stack.top.struc!.parsedDependencies) { - _logger.fine('Skipped dependencies.'); - } - - if (updateName) { - // If struct is seen, update it's name. - _stack.top.struc!.name = config.structDecl.renameUsingConfig(structName); - } - } - - return _stack.pop().struc; -} - -void _setStructMembers(clang_types.CXCursor cursor) { - _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0; - _stack.top.allignment = cursor.type().alignment(); - - final resultCode = clang.clang_visitChildren( - cursor, - Pointer.fromFunction(_structMembersVisitor, - clang_types.CXChildVisitResult.CXChildVisit_Break), - nullptr, - ); - - _logger.finest( - 'Opaque: ${_stack.top.isInComplete}, HasAttr: ${_stack.top.hasAttr}, AlignValue: ${_stack.top.allignment}, MaxChildAlignValue: ${_stack.top.maxChildAlignment}, PackValue: ${_stack.top.packValue}.'); - _stack.top.struc!.pack = _stack.top.packValue; - - visitChildrenResultChecker(resultCode); - - if (_stack.top.unimplementedMemberType) { - _logger.fine( - '---- Removed Struct members, reason: member with unimplementedtype ${cursor.completeStringRepr()}'); - _logger.warning( - 'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), struct member has an unsupported type.'); - } else if (_stack.top.flexibleArrayMember) { - _logger.fine( - '---- Removed Struct members, reason: incomplete array member ${cursor.completeStringRepr()}'); - _logger.warning( - 'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Flexible array members not supported.'); - } else if (_stack.top.bitFieldMember) { - _logger.fine( - '---- Removed Struct members, reason: bitfield members ${cursor.completeStringRepr()}'); - _logger.warning( - 'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Bit Field members not supported.'); - } else if (_stack.top.dartHandleMember && config.useDartHandle) { - _logger.fine( - '---- Removed Struct members, reason: Dart_Handle member. ${cursor.completeStringRepr()}'); - _logger.warning( - 'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Dart_Handle member not supported.'); - } else if (_stack.top.incompleteStructMember) { - _logger.fine( - '---- Removed Struct members, reason: Incomplete Nested Struct member. ${cursor.completeStringRepr()}'); - _logger.warning( - 'Removed All Struct Members from ${_stack.top.struc!.name}(${_stack.top.struc!.originalName}), Incomplete Nested Struct member not supported.'); - } - - // Clear all struct members if struct is incomplete. - if (_stack.top.isInComplete) { - _stack.top.struc!.members.clear(); - } - - // C allow empty structs, but it's undefined behaviour at runtine. So we need - // to mark a struct incomplete if it has no members. - _stack.top.struc!.isInComplete = - _stack.top.isInComplete || _stack.top.struc!.members.isEmpty; -} - -/// Visitor for the struct cursor [CXCursorKind.CXCursor_StructDecl]. -/// -/// Child visitor invoked on struct cursor. -int _structMembersVisitor(clang_types.CXCursor cursor, - clang_types.CXCursor parent, Pointer<Void> clientData) { - try { - if (cursor.kind == clang_types.CXCursorKind.CXCursor_FieldDecl) { - _logger.finer('===== member: ${cursor.completeStringRepr()}'); - - // Set maxChildAlignValue. - final align = cursor.type().alignment(); - if (align > _stack.top.maxChildAlignment) { - _stack.top.maxChildAlignment = align; - } - - final mt = cursor.type().toCodeGenType(); - if (mt.broadType == BroadType.IncompleteArray) { - // TODO(68): Structs with flexible Array Members are not supported. - _stack.top.flexibleArrayMember = true; - } - if (clang.clang_getFieldDeclBitWidth(cursor) != -1) { - // TODO(84): Struct with bitfields are not suppoorted. - _stack.top.bitFieldMember = true; - } - if (mt.broadType == BroadType.Handle) { - _stack.top.dartHandleMember = true; - } - if (mt.isIncompleteStruct) { - _stack.top.incompleteStructMember = true; - } - if (mt.getBaseType().broadType == BroadType.Unimplemented) { - _stack.top.unimplementedMemberType = true; - } - - _stack.top.struc!.members.add( - Member( - dartDoc: getCursorDocComment( - cursor, - nesting.length + commentPrefix.length, - ), - originalName: cursor.spelling(), - name: config.structDecl.renameMemberUsingConfig( - _stack.top.struc!.originalName, - cursor.spelling(), - ), - type: mt, - ), - ); - } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) { - _stack.top.hasPackedAttr = true; - } - } 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/sub_parsers/typedefdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart index 7c74aec..192c038 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/typedefdecl_parser.dart
@@ -10,8 +10,8 @@ import '../clang_bindings/clang_bindings.dart' as clang_types; import '../data.dart'; import '../sub_parsers/enumdecl_parser.dart'; -import '../sub_parsers/structdecl_parser.dart'; import '../utils.dart'; +import 'compounddecl_parser.dart'; final _logger = Logger('ffigen.header_parser.typedefdecl_parser'); @@ -82,8 +82,25 @@ _stack.top.binding = bindingsIndex.getSeenStruct(cursor.usr()); } else { // This will update the name of struct if already seen. - _stack.top.binding = - parseStructDeclaration(cursor, name: _stack.top.typedefName); + _stack.top.binding = parseCompoundDeclaration( + cursor, + CompoundType.struct, + name: _stack.top.typedefName, + ); + } + break; + case clang_types.CXCursorKind.CXCursor_UnionDecl: + if (_stack.top.typedefToPointer && + bindingsIndex.isSeenUnion(cursor.usr())) { + // Skip a typedef pointer if struct is seen. + _stack.top.binding = bindingsIndex.getSeenUnion(cursor.usr()); + } else { + // This will update the name of struct if already seen. + _stack.top.binding = parseCompoundDeclaration( + cursor, + CompoundType.union, + name: _stack.top.typedefName, + ); } break; case clang_types.CXCursorKind.CXCursor_EnumDecl:
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 69f7f13..5f59707 100644 --- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
@@ -12,9 +12,9 @@ import 'clang_bindings/clang_bindings.dart' as clang_types; import 'data.dart'; import 'includer.dart'; +import 'sub_parsers/compounddecl_parser.dart'; import 'sub_parsers/enumdecl_parser.dart'; import 'sub_parsers/functiondecl_parser.dart'; -import 'sub_parsers/structdecl_parser.dart'; import 'sub_parsers/typedefdecl_parser.dart'; import 'utils.dart'; @@ -51,7 +51,10 @@ addToBindings(parseTypedefDeclaration(cursor)); break; case clang_types.CXCursorKind.CXCursor_StructDecl: - addToBindings(parseStructDeclaration(cursor)); + addToBindings(parseCompoundDeclaration(cursor, CompoundType.struct)); + break; + case clang_types.CXCursorKind.CXCursor_UnionDecl: + addToBindings(parseCompoundDeclaration(cursor, CompoundType.union)); break; case clang_types.CXCursorKind.CXCursor_EnumDecl: addToBindings(parseEnumDeclaration(cursor));
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart index cd0445e..521e71e 100644 --- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart +++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -9,7 +9,7 @@ import '../clang_bindings/clang_bindings.dart' as clang_types; import '../data.dart'; -import '../sub_parsers/structdecl_parser.dart'; +import '../sub_parsers/compounddecl_parser.dart'; import '../translation_unit_parser.dart'; import '../type_extractor/cxtypekindmap.dart'; import '../utils.dart'; @@ -36,8 +36,9 @@ // Replace Pointer<_Dart_Handle> with Handle. if (config.useDartHandle && - s.broadType == BroadType.Struct && - s.struc!.usr == strings.dartHandleUsr) { + s.broadType == BroadType.Compound && + s.compound!.compoundType == CompoundType.struct && + s.compound!.usr == strings.dartHandleUsr) { return Type.handle(); } return Type.pointer(s); @@ -115,48 +116,69 @@ final cursor = clang.clang_getTypeDeclaration(cxtype); _logger.fine('${_padding}_extractfromRecord: ${cursor.completeStringRepr()}'); - switch (clang.clang_getCursorKind(cursor)) { - case clang_types.CXCursorKind.CXCursor_StructDecl: - final structUsr = cursor.usr(); + final cursorKind = clang.clang_getCursorKind(cursor); + if (cursorKind == clang_types.CXCursorKind.CXCursor_StructDecl || + cursorKind == clang_types.CXCursorKind.CXCursor_UnionDecl) { + final declUsr = cursor.usr(); - // Name of typedef (parentName) is used if available. - final structName = parentName ?? cursor.spelling(); + // Name of typedef (parentName) is used if available. + final declName = parentName ?? cursor.spelling(); - // Also add a struct binding, if its unseen. - // TODO(23): Check if we should auto add struct. - if (bindingsIndex.isSeenStruct(structUsr)) { - type = Type.struct(bindingsIndex.getSeenStruct(structUsr)!); + // Set includer functions according to compoundType. + final bool Function(String) isSeenDecl; + final Compound? Function(String) getSeenDecl; + final CompoundType compoundType; - // This will parse the dependencies if needed. - parseStructDeclaration( - cursor, - name: structName, - ignoreFilter: true, - pointerReference: pointerReference, - updateName: false, - ); - } else { - final struc = parseStructDeclaration( - cursor, - name: structName, - ignoreFilter: true, - pointerReference: pointerReference, - ); - type = Type.struct(struc!); + switch (cursorKind) { + case clang_types.CXCursorKind.CXCursor_StructDecl: + isSeenDecl = bindingsIndex.isSeenStruct; + getSeenDecl = bindingsIndex.getSeenStruct; + compoundType = CompoundType.struct; + break; + case clang_types.CXCursorKind.CXCursor_UnionDecl: + isSeenDecl = bindingsIndex.isSeenUnion; + getSeenDecl = bindingsIndex.getSeenUnion; + compoundType = CompoundType.union; + break; + default: + throw Exception('Unhandled compound type cursorkind.'); + } - // Add to bindings if it's not Dart_Handle and is unseen. - if (!(config.useDartHandle && structUsr == strings.dartHandleUsr)) { - addToBindings(struc); - } + // Also add a struct binding, if its unseen. + // TODO(23): Check if we should auto add compound declarations. + if (isSeenDecl(declUsr)) { + type = Type.compound(getSeenDecl(declUsr)!); + + // This will parse the dependencies if needed. + parseCompoundDeclaration( + cursor, + compoundType, + name: declName, + ignoreFilter: true, + pointerReference: pointerReference, + updateName: false, + ); + } else { + final struc = parseCompoundDeclaration( + cursor, + compoundType, + name: declName, + ignoreFilter: true, + pointerReference: pointerReference, + ); + type = Type.compound(struc!); + + // Add to bindings if it's not Dart_Handle and is unseen. + if (!(config.useDartHandle && declUsr == strings.dartHandleUsr)) { + addToBindings(struc); } - - break; - default: - _logger.fine( - 'typedeclarationCursorVisitor: _extractfromRecord: Not Implemented, ${cursor.completeStringRepr()}'); - return Type.unimplemented( - 'Type: ${cxtype.kindSpelling()} not implemented'); + } + } else { + _logger.fine( + 'typedeclarationCursorVisitor: _extractfromRecord: Not Implemented, ${cursor.completeStringRepr()}'); + return Type.unimplemented('Type: ${cxtype.kindSpelling()} not implemented'); } + return type; } @@ -173,7 +195,7 @@ final t = clang.clang_getArgType(cxtype, i); final pt = t.toCodeGenType(); - if (pt.isIncompleteStruct) { + if (pt.isIncompleteCompound) { return Type.unimplemented( 'Incomplete Struct by value in function parameter.'); } else if (pt.getBaseType().broadType == BroadType.Unimplemented) {
diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index fda7f65..272f21d 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart
@@ -327,6 +327,7 @@ class BindingsIndex { // Tracks if bindings are already seen, Map key is USR obtained from libclang. final Map<String, Struc> _structs = {}; + final Map<String, Union> _unions = {}; final Map<String, Func> _functions = {}; final Map<String, EnumClass> _enumClass = {}; final Map<String, Constant> _unnamedEnumConstants = {}; @@ -339,14 +340,26 @@ return _structs.containsKey(usr); } - void addStructToSeen(String usr, Struc struc) { - _structs[usr] = struc; + void addStructToSeen(String usr, Compound struc) { + _structs[usr] = struc as Struc; } Struc? getSeenStruct(String usr) { return _structs[usr]; } + bool isSeenUnion(String usr) { + return _unions.containsKey(usr); + } + + void addUnionToSeen(String usr, Compound union) { + _unions[usr] = union as Union; + } + + Union? getSeenUnion(String usr) { + return _unions[usr]; + } + bool isSeenFunc(String usr) { return _functions.containsKey(usr); }
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 0b5d2f1..b551ead 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -46,6 +46,7 @@ // Declarations. const functions = 'functions'; const structs = 'structs'; +const unions = 'unions'; const enums = 'enums'; const unnamedEnums = 'unnamed-enums'; const globals = 'globals'; @@ -58,10 +59,10 @@ const memberRename = 'member-rename'; const symbolAddress = 'symbol-address'; -const structDependencies = 'dependency-only'; -// Values for `structDependencies`. -const fullStructDependencies = 'full'; -const opaqueStructDependencies = 'opaque'; +const dependencyOnly = 'dependency-only'; +// Values for `compoundDependencies`. +const fullCompoundDependencies = 'full'; +const opaqueCompoundDependencies = 'opaque'; const structPack = 'pack'; const Map<Object, int?> packingValuesMap = {
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index ddacda0..54793af 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml
@@ -3,12 +3,12 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 3.0.0 +version: 3.1.0-dev.0 homepage: https://github.com/dart-lang/ffigen description: Generator for FFI bindings, using LibClang to parse C header files. environment: - sdk: '>=2.13.0 <3.0.0' + sdk: '>=2.14.0-115.0.dev <3.0.0' dependencies: ffi: ^1.0.0
diff --git a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart index 7b0fa29..79ac352 100644 --- a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart +++ b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart
@@ -404,6 +404,49 @@ ); _matchLib(library, 'packed_structs'); }); + test('Union Bindings', () { + final struct1 = Struc(name: 'Struct1', members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8)) + ]); + final union1 = Union(name: 'Union1', members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8)) + ]); + final library = Library( + name: 'Bindings', + bindings: [ + struct1, + union1, + Union(name: 'EmptyUnion'), + Union(name: 'Primitives', members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8)), + Member(name: 'b', type: Type.nativeType(SupportedNativeType.Int32)), + Member(name: 'c', type: Type.nativeType(SupportedNativeType.Float)), + Member(name: 'd', type: Type.nativeType(SupportedNativeType.Double)), + ]), + Union(name: 'PrimitivesWithPointers', members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Int8)), + Member(name: 'b', type: Type.nativeType(SupportedNativeType.Float)), + Member( + name: 'c', + type: Type.pointer(Type.nativeType(SupportedNativeType.Double))), + Member(name: 'd', type: Type.pointer(Type.union(union1))), + Member(name: 'd', type: Type.pointer(Type.struct(struct1))), + ]), + Union(name: 'WithArray', members: [ + Member( + name: 'a', + type: Type.constantArray( + 10, Type.nativeType(SupportedNativeType.Int8))), + Member(name: 'b', type: Type.constantArray(10, Type.union(union1))), + Member(name: 'b', type: Type.constantArray(10, Type.struct(struct1))), + Member( + name: 'c', + type: Type.constantArray(10, Type.pointer(Type.union(union1)))), + ]), + ], + ); + _matchLib(library, 'unions'); + }); } /// Utility to match expected bindings to the generated bindings.
diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_unions_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_unions_bindings.dart new file mode 100644 index 0000000..60399e5 --- /dev/null +++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_unions_bindings.dart
@@ -0,0 +1,58 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +class Struct1 extends ffi.Struct { + @ffi.Int8() + external int a; +} + +class Union1 extends ffi.Union { + @ffi.Int8() + external int a; +} + +class EmptyUnion extends ffi.Opaque {} + +class Primitives extends ffi.Union { + @ffi.Int8() + external int a; + + @ffi.Int32() + external int b; + + @ffi.Float() + external double c; + + @ffi.Double() + external double d; +} + +class PrimitivesWithPointers extends ffi.Union { + @ffi.Int8() + external int a; + + @ffi.Float() + external double b; + + external ffi.Pointer<ffi.Double> c; + + external ffi.Pointer<Union1> d; + + external ffi.Pointer<Struct1> d_1; +} + +class WithArray extends ffi.Union { + @ffi.Array.multi([10]) + external ffi.Array<ffi.Int8> a; + + @ffi.Array.multi([10]) + external ffi.Array<Union1> b; + + @ffi.Array.multi([10]) + external ffi.Array<Struct1> b_1; + + @ffi.Array.multi([10]) + external ffi.Array<ffi.Pointer<Union1>> c; +}
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart index 0c3498f..c37dbaf 100644 --- a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart +++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
@@ -29,6 +29,17 @@ late final _func_ptr = _lookup<ffi.NativeFunction<_c_func>>('func'); late final _dart_func _func = _func_ptr.asFunction<_dart_func>(); + + ffi.Pointer<UB> func2( + ffi.Pointer<UA> a, + ) { + return _func2( + a, + ); + } + + late final _func2_ptr = _lookup<ffi.NativeFunction<_c_func2>>('func2'); + late final _dart_func2 _func2 = _func2_ptr.asFunction<_dart_func2>(); } class B extends ffi.Opaque {} @@ -48,6 +59,23 @@ external D d; } +class UB extends ffi.Opaque {} + +class UA extends ffi.Opaque {} + +class UC extends ffi.Opaque {} + +class UD extends ffi.Union { + @ffi.Int32() + external int a; +} + +class UE extends ffi.Union { + external ffi.Pointer<UC> c; + + external UD d; +} + typedef _c_func = ffi.Pointer<B> Function( ffi.Pointer<A> a, ); @@ -55,3 +83,11 @@ typedef _dart_func = ffi.Pointer<B> Function( ffi.Pointer<A> a, ); + +typedef _c_func2 = ffi.Pointer<UB> Function( + ffi.Pointer<UA> a, +); + +typedef _dart_func2 = ffi.Pointer<UB> Function( + ffi.Pointer<UA> a, +);
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart new file mode 100644 index 0000000..a06659e --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_unions_bindings.dart
@@ -0,0 +1,74 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +/// Unions Test +class NativeLibrary { + /// Holds the symbol lookup function. + final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + NativeLibrary(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + NativeLibrary.fromLookup( + ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) + lookup) + : _lookup = lookup; + + void func1( + ffi.Pointer<Union2> s, + ) { + return _func1( + s, + ); + } + + late final _func1_ptr = _lookup<ffi.NativeFunction<_c_func1>>('func1'); + late final _dart_func1 _func1 = _func1_ptr.asFunction<_dart_func1>(); + + void func2( + ffi.Pointer<Union3> s, + ) { + return _func2( + s, + ); + } + + late final _func2_ptr = _lookup<ffi.NativeFunction<_c_func2>>('func2'); + late final _dart_func2 _func2 = _func2_ptr.asFunction<_dart_func2>(); +} + +class Union1 extends ffi.Union { + @ffi.Int32() + external int a; +} + +class Union2 extends ffi.Union { + external Union1 a; +} + +class Union3 extends ffi.Opaque {} + +class Union4 extends ffi.Opaque {} + +class Union5 extends ffi.Opaque {} + +typedef _c_func1 = ffi.Void Function( + ffi.Pointer<Union2> s, +); + +typedef _dart_func1 = void Function( + ffi.Pointer<Union2> s, +); + +typedef _c_func2 = ffi.Void Function( + ffi.Pointer<Union3> s, +); + +typedef _dart_func2 = void Function( + ffi.Pointer<Union3> s, +);
diff --git a/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h index fa24993..e30b260 100644 --- a/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h +++ b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h
@@ -34,3 +34,36 @@ struct C *c; struct D d; }; + +// Opaque. +union UA +{ + int a; +}; + +// Opaque. +union UB +{ + int a; +}; + +union UB *func2(union UA *a); + +// Opaque. +union UC +{ + int a; +}; + +// Full (excluded, but used by value). +union UD +{ + int a; +}; + +// Full (included) +union UE +{ + union UC *c; + union UD d; +};
diff --git a/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart index aea5468..101e8db 100644 --- a/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart +++ b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart
@@ -28,7 +28,11 @@ ${strings.structs}: ${strings.include}: - 'E' - ${strings.structDependencies}: ${strings.opaqueStructDependencies} + ${strings.dependencyOnly}: ${strings.opaqueCompoundDependencies} +${strings.unions}: + ${strings.include}: + - 'UE' + ${strings.dependencyOnly}: ${strings.opaqueCompoundDependencies} ''') as yaml.YamlMap), ); });
diff --git a/pkgs/ffigen/test/header_parser_tests/unions.h b/pkgs/ffigen/test/header_parser_tests/unions.h new file mode 100644 index 0000000..1bd9ae1 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/unions.h
@@ -0,0 +1,38 @@ +// Copyright (c) 2021, 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. + +union Union1 +{ + int a; +}; + +union Union2 +{ + union Union1 a; +}; + +// Should be marked incomplete, long double not supported. +union Union3 +{ + long double a; +}; + +// All members should be removed, Bit fields are not supported. +union Union4 +{ + int a : 3; + int : 2; // Unnamed bit field. +}; + +// All members should be removed, Incomplete union members are not supported. +union Union5 +{ + int a; + union Union3 s; // Incomplete nested union. +}; + +void func1(union Union2 *s); + +// Incomplete array parameter will be treated as a pointer. +void func2(union Union3 s[]);
diff --git a/pkgs/ffigen/test/header_parser_tests/unions_test.dart b/pkgs/ffigen/test/header_parser_tests/unions_test.dart new file mode 100644 index 0000000..699f93d --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/unions_test.dart
@@ -0,0 +1,45 @@ +// Copyright (c) 2021, 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; +void main() { + group('packed_structs_test', () { + setUpAll(() { + logWarnings(Level.SEVERE); + actual = parser.parse( + Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Unions Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/unions.h' + ''') as yaml.YamlMap), + ); + }); + + test('Expected bindings', () { + matchLibraryWithExpected(actual, [ + 'test', + 'debug_generated', + 'unions_test_output.dart' + ], [ + 'test', + 'header_parser_tests', + 'expected_bindings', + '_expected_unions_bindings.dart' + ]); + }); + }); +}