[ffigen] Support for Packed struct (#205)
diff --git a/pkgs/ffigen/.github/workflows/test-package.yml b/pkgs/ffigen/.github/workflows/test-package.yml index 2ee8d6a..642fed7 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.12.0] + sdk: [2.13.0-211.6.beta] # TODO: revert to 2.13. 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.12.0 + sdk: 2.13.0-211.6.beta # TODO: revert to 2.13. - 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 1b811cc..82b5807 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,8 @@ +# 2.5.0-beta.1 +- Added support for `Packed` structs. Packed annotations are generated +automatically but can be overriden using `structs -> pack` config. +- Updated sdk constraints to `>=2.13.0-211.6.beta`. + # 2.4.2 - Fix issues due to declarations having duplicate names. - Fix name conflict of declaration with ffi library prefix.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index cc1aeac..d8cac98 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -232,6 +232,27 @@ </td> </tr> <tr> + <td>structs -> pack</td> + <td>Override the @Packed(X) annotation for generated structs.<br><br> + <i>Options - none, 1, 2, 4, 8, 16</i><br> + You can use RegExp to match with the <b>generated</b> names.<br><br> + Note: Ffigen can only reliably identify packing specified using + __attribute__((__packed__)). However, structs packed using + `#pragma pack(...)` or any other way could <i>potentially</i> be incorrect + in which case you can override the generated annotations. + </td> + <td> + +```yaml +structs: + pack: + # Matches with the generated name. + 'NoPackStruct': none # No packing + '.*': 1 # Pack all structs with value 1 +``` + </td> + </tr> + <tr> <td>array-workaround</td> <td>Should generate workaround for fixed arrays in Structs. See <a href="#array-workaround">Array Workaround</a><br> <b>Default: false</b>
diff --git a/pkgs/ffigen/lib/src/code_generator/library.dart b/pkgs/ffigen/lib/src/code_generator/library.dart index e37a062..c29313f 100644 --- a/pkgs/ffigen/lib/src/code_generator/library.dart +++ b/pkgs/ffigen/lib/src/code_generator/library.dart
@@ -5,9 +5,11 @@ import 'dart:io'; import 'package:cli_util/cli_util.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'binding.dart'; +import 'struc.dart'; import 'utils.dart'; import 'writer.dart'; @@ -28,6 +30,7 @@ String? header, bool dartBool = true, bool sort = false, + StructPackingOverride? packingOverride, }) { if (sort) _sort(); @@ -38,6 +41,16 @@ _resolveIfNameConflicts(declConflictHandler, b); } + // Override pack values according to config. We do this after declaration + // conflicts have been handled so that users can target the generated names. + if (packingOverride != null) { + for (final b in bindings) { + if (b is Struc && packingOverride.isOverriden(b.name)) { + b.pack = packingOverride.getOverridenPackValue(b.name); + } + } + } + // Seperate bindings which require lookup. final lookUpBindings = bindings.whereType<LookUpBinding>().toList(); final noLookUpBindings = bindings.whereType<NoLookUpBinding>().toList();
diff --git a/pkgs/ffigen/lib/src/code_generator/struc.dart b/pkgs/ffigen/lib/src/code_generator/struc.dart index 2fc96d6..37edfa0 100644 --- a/pkgs/ffigen/lib/src/code_generator/struc.dart +++ b/pkgs/ffigen/lib/src/code_generator/struc.dart
@@ -44,6 +44,9 @@ 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; @@ -52,6 +55,7 @@ String? originalName, required String name, this.isInComplete = false, + this.pack, String? dartDoc, List<Member>? members, }) : members = members ?? [], @@ -105,6 +109,10 @@ /// 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');
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 2830366..cacc7fa 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -80,6 +80,10 @@ StructDependencies get structDependencies => _structDependencies; late StructDependencies _structDependencies; + /// Holds config for how struct packing should be overriden. + StructPackingOverride get structPackingOverride => _structPackingOverride; + late StructPackingOverride _structPackingOverride; + /// If tool should generate array workarounds. /// /// If false(default), structs with inline array members will have all its @@ -335,6 +339,15 @@ extractedResult: (dynamic result) => _structDependencies = result as StructDependencies, ), + [strings.structs, strings.structPack]: + Specification<StructPackingOverride>( + requirement: Requirement.no, + validator: structPackingOverrideValidator, + extractor: structPackingOverrideExtractor, + defaultValue: () => StructPackingOverride(), + extractedResult: (dynamic result) => + _structPackingOverride = result as StructPackingOverride, + ), [strings.arrayWorkaround]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator,
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index 0240c19..7fcc352 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -31,6 +31,35 @@ enum StructDependencies { full, opaque } +/// Holds config for how Structs Packing will be overriden. +class StructPackingOverride { + final Map<RegExp, int?> _matcherMap; + + StructPackingOverride({Map<RegExp, int?>? matcherMap}) + : _matcherMap = matcherMap ?? {}; + + /// Returns true if the user has overriden the pack value. + bool isOverriden(String name) { + for (final key in _matcherMap.keys) { + if (quiver.matchesFull(key, name)) { + return true; + } + } + return false; + } + + /// Returns pack value for [name]. Ensure that value [isOverriden] before + /// using the returned value. + int? getOverridenPackValue(String name) { + for (final opv in _matcherMap.entries) { + if (quiver.matchesFull(opv.key, name)) { + return opv.value; + } + } + return null; + } +} + /// Represents a single specification in configurations. /// /// [E] is the return type of the extractedResult.
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index 6875af1..ff4a44a 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -699,3 +699,30 @@ } return result; } + +StructPackingOverride structPackingOverrideExtractor(dynamic value) { + final matcherMap = <RegExp, int?>{}; + for (final key in value.keys) { + matcherMap[RegExp(key as String, dotAll: true)] = + strings.packingValuesMap[value[key]]; + } + return StructPackingOverride(matcherMap: matcherMap); +} + +bool structPackingOverrideValidator(List<String> name, dynamic value) { + var _result = true; + + if (!checkType<YamlMap>([...name], value)) { + _result = false; + } else { + for (final key in value.keys) { + if (!(strings.packingValuesMap.keys.contains(value[key]))) { + _logger.severe( + "'$name -> $key' must be one of the following - ${strings.packingValuesMap.keys.toList()}"); + _result = false; + } + } + } + + return _result; +}
diff --git a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart index 71c02c9..2c69fa7 100644 --- a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart +++ b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -376,6 +376,21 @@ late final _dart_clang_getCursorKind _clang_getCursorKind = _clang_getCursorKind_ptr.asFunction<_dart_clang_getCursorKind>(); + /// Determine whether the given cursor has any attributes. + int clang_Cursor_hasAttrs( + CXCursor C, + ) { + return _clang_Cursor_hasAttrs( + C, + ); + } + + late final _clang_Cursor_hasAttrs_ptr = + _lookup<ffi.NativeFunction<_c_clang_Cursor_hasAttrs>>( + 'clang_Cursor_hasAttrs'); + late final _dart_clang_Cursor_hasAttrs _clang_Cursor_hasAttrs = + _clang_Cursor_hasAttrs_ptr.asFunction<_dart_clang_Cursor_hasAttrs>(); + /// Retrieve the physical location of the source constructor referenced /// by the given cursor. /// @@ -772,6 +787,30 @@ late final _dart_clang_Type_getNamedType _clang_Type_getNamedType = _clang_Type_getNamedType_ptr.asFunction<_dart_clang_Type_getNamedType>(); + /// Return the alignment of a type in bytes as per C++[expr.alignof] + /// standard. + /// + /// If the type declaration is invalid, CXTypeLayoutError_Invalid is returned. + /// If the type declaration is an incomplete type, CXTypeLayoutError_Incomplete + /// is returned. + /// If the type declaration is a dependent type, CXTypeLayoutError_Dependent is + /// returned. + /// If the type declaration is not a constant size type, + /// CXTypeLayoutError_NotConstantSize is returned. + int clang_Type_getAlignOf( + CXType T, + ) { + return _clang_Type_getAlignOf( + T, + ); + } + + late final _clang_Type_getAlignOf_ptr = + _lookup<ffi.NativeFunction<_c_clang_Type_getAlignOf>>( + 'clang_Type_getAlignOf'); + late final _dart_clang_Type_getAlignOf _clang_Type_getAlignOf = + _clang_Type_getAlignOf_ptr.asFunction<_dart_clang_Type_getAlignOf>(); + /// Determine whether the given cursor represents an anonymous /// tag or namespace int clang_Cursor_isAnonymous( @@ -2690,6 +2729,14 @@ CXCursor arg0, ); +typedef _c_clang_Cursor_hasAttrs = ffi.Uint32 Function( + CXCursor C, +); + +typedef _dart_clang_Cursor_hasAttrs = int Function( + CXCursor C, +); + typedef _c_clang_getCursorLocation = CXSourceLocation Function( CXCursor arg0, ); @@ -2870,6 +2917,14 @@ CXType T, ); +typedef _c_clang_Type_getAlignOf = ffi.Int64 Function( + CXType T, +); + +typedef _dart_clang_Type_getAlignOf = int Function( + CXType T, +); + typedef _c_clang_Cursor_isAnonymous = ffi.Uint32 Function( CXCursor C, );
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index ff28679..ddcd9c6 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -30,6 +30,7 @@ header: config.preamble, dartBool: config.dartBool, sort: config.sort, + packingOverride: config.structPackingOverride, ); return library;
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 index d9c4023..c27d841 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart
@@ -8,6 +8,7 @@ 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'; @@ -33,6 +34,39 @@ (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(); } @@ -62,7 +96,6 @@ if (isForwardDeclaration(cursor)) { cursor = clang.clang_getCursorDefinition(cursor); } - final structUsr = cursor.usr(); final structName = name ?? cursor.spelling(); @@ -119,8 +152,8 @@ } void _setStructMembers(clang_types.CXCursor cursor) { - _stack.top.arrayMember = false; - _stack.top.unimplementedMemberType = false; + _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0; + _stack.top.allignment = cursor.type().alignment(); final resultCode = clang.clang_visitChildren( cursor, @@ -129,6 +162,10 @@ 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.arrayMember && !config.arrayWorkaround) { @@ -183,6 +220,12 @@ 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.ConstantArray) { _stack.top.arrayMember = true; @@ -219,6 +262,8 @@ type: mt, ), ); + } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) { + _stack.top.hasPackedAttr = true; } } catch (e, s) { _logger.severe(e);
diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index e1b205c..fda7f65 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart
@@ -236,6 +236,10 @@ return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose(); } + int alignment() { + return clang.clang_Type_getAlignOf(this); + } + /// For debugging: returns [spelling] [kind] [kindSpelling]. String completeStringRepr() { final s =
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 2f592a2..76ed940 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -64,6 +64,16 @@ const fullStructDependencies = 'full'; const opaqueStructDependencies = 'opaque'; +const structPack = 'pack'; +const Map<Object, int?> packingValuesMap = { + 'none': null, + 1: 1, + 2: 2, + 4: 4, + 8: 8, + 16: 16, +}; + const sizemap = 'size-map'; const typedefmap = 'typedef-map';
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index d5ad721..e761291 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: 2.4.2 +version: 2.5.0-beta.1 homepage: https://github.com/dart-lang/ffigen description: Generator for FFI bindings, using LibClang to parse C header files. environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.13.0-211.6.beta <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 806ae22..7b0fa29 100644 --- a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart +++ b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart
@@ -378,6 +378,32 @@ ); _matchLib(library, 'sort_bindings'); }); + test('Pack Structs', () { + final library = Library( + name: 'Bindings', + bindings: [ + Struc(name: 'NoPacking', pack: null, members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)), + ]), + Struc(name: 'Pack1', pack: 1, members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)), + ]), + Struc(name: 'Pack2', pack: 2, members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)), + ]), + Struc(name: 'Pack2', pack: 4, members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)), + ]), + Struc(name: 'Pack2', pack: 8, members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)), + ]), + Struc(name: 'Pack16', pack: 16, members: [ + Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)), + ]), + ], + ); + _matchLib(library, 'packed_structs'); + }); } /// Utility to match expected bindings to the generated bindings.
diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_packed_structs_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_packed_structs_bindings.dart new file mode 100644 index 0000000..9c647d7 --- /dev/null +++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_packed_structs_bindings.dart
@@ -0,0 +1,39 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +class NoPacking extends ffi.Struct { + @ffi.Uint8() + external int a; +} + +@ffi.Packed(1) +class Pack1 extends ffi.Struct { + @ffi.Uint8() + external int a; +} + +@ffi.Packed(2) +class Pack2 extends ffi.Struct { + @ffi.Uint8() + external int a; +} + +@ffi.Packed(4) +class Pack2_1 extends ffi.Struct { + @ffi.Uint8() + external int a; +} + +@ffi.Packed(8) +class Pack2_2 extends ffi.Struct { + @ffi.Uint8() + external int a; +} + +@ffi.Packed(16) +class Pack16 extends ffi.Struct { + @ffi.Uint8() + external int a; +}
diff --git a/pkgs/ffigen/test/config_tests/packed_struct_override_test.dart b/pkgs/ffigen/test/config_tests/packed_struct_override_test.dart new file mode 100644 index 0000000..fd8be93 --- /dev/null +++ b/pkgs/ffigen/test/config_tests/packed_struct_override_test.dart
@@ -0,0 +1,62 @@ +// 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/ffigen.dart'; +import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/strings.dart' as strings; +import 'package:yaml/yaml.dart' as yaml; +import 'package:test/test.dart'; + +import '../test_utils.dart'; + +late Library actual, expected; + +void main() { + group('packed_struct_override_test', () { + test('Invalid Packed Config values', () { + const baseYaml = '''${strings.name}: 'NativeLibrary' +${strings.description}: 'Packed Struct Override Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/packed_structs.h' +${strings.structs}: + ${strings.structPack}: + '''; + expect( + () => Config.fromYaml( + yaml.loadYaml(baseYaml + "'.*': null") as yaml.YamlMap), + throwsA(TypeMatcher<FormatException>())); + expect( + () => Config.fromYaml( + yaml.loadYaml(baseYaml + "'.*': 3") as yaml.YamlMap), + throwsA(TypeMatcher<FormatException>())); + expect( + () => Config.fromYaml( + yaml.loadYaml(baseYaml + "'.*': 32") as yaml.YamlMap), + throwsA(TypeMatcher<FormatException>())); + }); + test('Override values', () { + final config = Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Packed Struct Override Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/packed_structs.h' +${strings.structs}: + ${strings.structPack}: + 'Normal.*': 1 + 'StructWithAttr': 2 + 'PackedAttr': none + ''') as yaml.YamlMap); + + final library = parse(config); + + expect((library.getBinding('NormalStruct1') as Struc).pack, 1); + expect((library.getBinding('StructWithAttr') as Struc).pack, 2); + expect((library.getBinding('PackedAttr') as Struc).pack, null); + }); + }); +}
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart new file mode 100644 index 0000000..d27adc4 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart
@@ -0,0 +1,49 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +class NormalStruct1 extends ffi.Struct { + @ffi.Int8() + external int a; +} + +/// Should not be packed. +class StructWithAttr extends ffi.Struct { + external ffi.Pointer<ffi.Int32> a; + + external ffi.Pointer<ffi.Int32> b; +} + +/// Should be packed with 1. +@ffi.Packed(1) +class PackedAttr extends ffi.Struct { + @ffi.Int32() + external int a; +} + +/// Should be packed with 8. +@ffi.Packed(8) +class PackedAttrAlign8 extends ffi.Struct { + @ffi.Int32() + external int a; +} + +/// Should be packed with 2. +@ffi.Packed(2) +class Pack2WithPragma extends ffi.Struct { + @ffi.Int32() + external int a; +} + +/// Should be packed with 4. +@ffi.Packed(4) +class Pack4WithPragma extends ffi.Struct { + @ffi.Int64() + external int a; +} + +class NormalStruct2 extends ffi.Struct { + @ffi.Int8() + external int a; +}
diff --git a/pkgs/ffigen/test/header_parser_tests/packed_structs.h b/pkgs/ffigen/test/header_parser_tests/packed_structs.h new file mode 100644 index 0000000..2edec80 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/packed_structs.h
@@ -0,0 +1,41 @@ +// 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. + +struct NormalStruct1 +{ + char a; +}; + +/// Should not be packed. +struct StructWithAttr +{ + int *a; + int *b; +} __attribute__((annotate("Attr is not __packed__"))); + +/// Should be packed with 1. +struct PackedAttr{ + int a; +} __attribute__((__packed__)); + +/// Should be packed with 8. +struct PackedAttrAlign8{ + int a; +} __attribute__((__packed__, aligned(8))); + +#pragma pack(push, 2) +/// Should be packed with 2. +struct Pack2WithPragma{ + int a; +}; +#pragma pack(4) +/// Should be packed with 4. +struct Pack4WithPragma{ + long long a; +}; +#pragma pack(pop) +struct NormalStruct2 +{ + char a; +};
diff --git a/pkgs/ffigen/test/header_parser_tests/packed_structs_test.dart b/pkgs/ffigen/test/header_parser_tests/packed_structs_test.dart new file mode 100644 index 0000000..e334fab --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/packed_structs_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/header_parser.dart' as parser; +import 'package:ffigen/src/config_provider.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; +import 'package:ffigen/src/strings.dart' as strings; + +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}: 'Packed Structs Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/packed_structs.h' + ''') as yaml.YamlMap), + ); + }); + + test('Expected bindings', () { + matchLibraryWithExpected(actual, [ + 'test', + 'debug_generated', + 'packed_structs_test_output.dart' + ], [ + 'test', + 'header_parser_tests', + 'expected_bindings', + '_expected_packed_structs_bindings.dart' + ]); + }); + }); +}
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml index f61c097..87f6bb0 100644 --- a/pkgs/ffigen/tool/libclang_config.yaml +++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -76,6 +76,7 @@ - clang_getPointeeType - clang_getCanonicalType - clang_Type_getNamedType + - clang_Type_getAlignOf - clang_getTypeDeclaration - clang_getTypedefDeclUnderlyingType - clang_getCursorSpelling @@ -106,3 +107,4 @@ - clang_Cursor_isFunctionInlined - clang_getCursorDefinition - clang_Cursor_isNull + - clang_Cursor_hasAttrs