blob: d0d5cd6d564ede7be2eba2c55da003b2a1d11b6a [file] [log] [blame]
// 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, {
/// 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,
}) {
_stack.push(_ParsedCompound());
// 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;
}
// Parse the cursor definition instead, if this is a forward declaration.
if (isForwardDeclaration(cursor)) {
cursor = clang.clang_getCursorDefinition(cursor);
}
final declUsr = cursor.usr();
final String declName;
// Only set name using USR if the type is not Anonymous (A struct is anonymous
// if it has no name, is not inside any typedef and declared inline inside
// another declaration).
if (clang.clang_Cursor_isAnonymous(cursor) == 0) {
// This gives the significant name, i.e name of the struct if defined or
// name of the first typedef declaration that refers to it.
declName = declUsr.split('@').last;
} else {
// Empty names are treated as inline declarations.
declName = '';
}
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 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.');
}
}
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;
}