blob: b9b79b3ce5985b83678e038ec44960c2f2489e38 [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 '../../code_generator.dart';
import '../../config_provider/config.dart';
import '../../config_provider/config_types.dart';
import '../../context.dart';
import '../../strings.dart' as strings;
import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../utils.dart';
import 'api_availability.dart';
Compound? parseStructDeclaration(
clang_types.CXCursor cursor,
Context context,
) => _parseCompoundDeclaration(
cursor,
context,
'Struct',
context.config.structs,
Struct.new,
);
Compound? parseUnionDeclaration(clang_types.CXCursor cursor, Context context) =>
_parseCompoundDeclaration(
cursor,
context,
'Union',
context.config.unions,
Union.new,
);
/// Holds temporary information regarding [compound] while parsing.
class _ParsedCompound {
final Context context;
Compound compound;
bool unimplementedMemberType = false;
bool flexibleArrayMember = false;
bool bitFieldMember = false;
bool dartHandleMember = false;
bool incompleteCompoundMember = false;
_ParsedCompound(this.context, this.compound);
bool get isIncomplete =>
unimplementedMemberType ||
flexibleArrayMember ||
bitFieldMember ||
dartHandleMember ||
incompleteCompoundMember ||
alignment == clang_types.CXTypeLayoutError.CXTypeLayoutError_Incomplete;
// 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 alignment = 0;
bool get _isPacked {
if (!hasAttr || isIncomplete) return false;
if (hasPackedAttr) return true;
return maxChildAlignment > alignment;
}
Declarations get compoundConfig =>
compound is Struct ? context.config.structs : context.config.unions;
/// Returns pack value of a struct depending on config, returns null for no
/// packing.
int? get packValue {
if (compound is Struct && _isPacked && !isIncomplete) {
if (strings.packingValuesMap.containsKey(alignment)) {
return alignment;
} else {
context.logger.warning(
'Unsupported pack value "$alignment" for Struct '
'"${compound.originalName}".',
);
return null;
}
} else {
return null;
}
}
}
/// Parses a compound declaration.
Compound? _parseCompoundDeclaration(
clang_types.CXCursor cursor,
Context context,
String className,
Declarations configDecl,
Compound Function({
String? usr,
String? originalName,
required String name,
String? dartDoc,
required Context context,
String? nativeType,
})
constructor,
) {
// Parse the cursor definition instead, if this is a forward declaration.
final usr = cursor.usr();
final cachedCompound = context.bindingsIndex.getSeenCompound(usr);
if (cachedCompound != null) return cachedCompound;
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 = usr.split('@').last;
} else {
// Empty names are treated as inline declarations.
declName = '';
}
final apiAvailability = ApiAvailability.fromCursor(cursor, context);
if (apiAvailability.availability == Availability.none) {
context.logger.info('Omitting deprecated $className $declName');
return null;
}
final decl = Declaration(usr: usr, originalName: declName);
final Compound compound;
if (declName.isEmpty) {
cursor = context.cursorIndex.getDefinition(cursor);
compound = constructor(
name: 'Unnamed$className',
usr: usr,
dartDoc: getCursorDocComment(
context,
cursor,
availability: apiAvailability.dartDoc,
),
context: context,
nativeType: cursor.type().spelling(),
);
} else {
cursor = context.cursorIndex.getDefinition(cursor);
context.logger.fine(
'++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}',
);
compound = constructor(
usr: usr,
originalName: declName,
name: configDecl.rename(decl),
dartDoc: getCursorDocComment(
context,
cursor,
availability: apiAvailability.dartDoc,
),
context: context,
nativeType: cursor.type().spelling(),
);
}
context.bindingsIndex.addCompoundToSeen(usr, compound);
return compound;
}
void fillCompoundMembersIfNeeded(
Compound compound,
clang_types.CXCursor cursor,
Context context,
) {
if (compound.parsedDependencies) return;
final logger = context.logger;
cursor = context.cursorIndex.getDefinition(cursor);
final parsed = _ParsedCompound(context, compound);
final className = compound is Struct ? 'Struct' : 'Union';
parsed.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
parsed.alignment = cursor.type().alignment();
compound.parsedDependencies = true; // Break cycles.
cursor.visitChildren((cursor) => _compoundMembersVisitor(cursor, parsed));
logger.finest(
'Opaque: ${parsed.isIncomplete}, HasAttr: ${parsed.hasAttr}, '
'AlignValue: ${parsed.alignment}, '
'MaxChildAlignValue: ${parsed.maxChildAlignment}, '
'PackValue: ${parsed.packValue}.',
);
if (compound is Struct) {
compound.pack = parsed.packValue;
}
if (parsed.unimplementedMemberType) {
logger.fine(
'---- Removed $className members, reason: member with '
'unimplementedtype ${cursor.completeStringRepr()}',
);
logger.warning(
'Removed All $className Members from ${compound.originalName}'
'(${compound.originalName}), struct member has an unsupported type.',
);
} else if (parsed.flexibleArrayMember) {
logger.fine(
'---- Removed $className members, reason: incomplete array '
'member ${cursor.completeStringRepr()}',
);
logger.warning(
'Removed All $className Members from ${compound.originalName}'
'(${compound.originalName}), Flexible array members not supported.',
);
} else if (parsed.bitFieldMember) {
logger.fine(
'---- Removed $className members, reason: bitfield members '
'${cursor.completeStringRepr()}',
);
logger.warning(
'Removed All $className Members from ${compound.originalName}'
'(${compound.originalName}), Bit Field members not supported.',
);
} else if (parsed.dartHandleMember) {
logger.fine(
'---- Removed $className members, reason: Dart_Handle member. '
'${cursor.completeStringRepr()}',
);
logger.warning(
'Removed All $className Members from ${compound.originalName}'
'(${compound.originalName}), Dart_Handle member not supported.',
);
} else if (parsed.incompleteCompoundMember) {
logger.fine(
'---- Removed $className members, reason: Incomplete Nested Struct '
'member. ${cursor.completeStringRepr()}',
);
logger.warning(
'Removed All $className Members from ${compound.originalName}'
'(${compound.originalName}), Incomplete Nested Struct member not '
'supported.',
);
}
// Clear all members if declaration is incomplete.
if (parsed.isIncomplete) {
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.
compound.isIncomplete = parsed.isIncomplete || compound.members.isEmpty;
}
/// Visitor for the struct/union cursor
/// [clang_types.CXCursorKind.CXCursor_StructDecl]/
/// [clang_types.CXCursorKind.CXCursor_UnionDecl].
///
/// Child visitor invoked on struct/union cursor.
void _compoundMembersVisitor(
clang_types.CXCursor cursor,
_ParsedCompound parsed,
) {
final context = parsed.context;
final compoundConf = parsed.compoundConfig;
final decl = Declaration(
usr: parsed.compound.usr,
originalName: parsed.compound.originalName,
);
try {
switch (cursor.kind) {
case clang_types.CXCursorKind.CXCursor_FieldDecl:
context.logger.finer('===== member: ${cursor.completeStringRepr()}');
// Set maxChildAlignValue.
final align = cursor.type().alignment();
if (align > parsed.maxChildAlignment) {
parsed.maxChildAlignment = align;
}
final mt = cursor.toCodeGenType(context);
if (mt is IncompleteArray) {
// TODO(https://github.com/dart-lang/ffigen/issues/68): Structs with
// flexible Array Members are not supported.
parsed.flexibleArrayMember = true;
}
if (clang.clang_getFieldDeclBitWidth(cursor) != -1) {
// TODO(https://github.com/dart-lang/ffigen/issues/84): Struct with
// bitfields are not suppoorted.
parsed.bitFieldMember = true;
}
if (mt is HandleType) {
parsed.dartHandleMember = true;
}
if (mt.isIncompleteCompound) {
parsed.incompleteCompoundMember = true;
}
if (mt.baseType is UnimplementedType) {
parsed.unimplementedMemberType = true;
}
parsed.compound.members.add(
CompoundMember(
dartDoc: getCursorDocComment(
context,
cursor,
indent: nesting.length + commentPrefix.length,
),
originalName: cursor.spelling(),
name: compoundConf.renameMember(decl, cursor.spelling()),
type: mt,
),
);
break;
case clang_types.CXCursorKind.CXCursor_PackedAttr:
parsed.hasPackedAttr = true;
break;
case clang_types.CXCursorKind.CXCursor_UnionDecl:
case clang_types.CXCursorKind.CXCursor_StructDecl:
// If the union/struct are anonymous, then we need to add them now,
// otherwise they will be added in the next iteration.
if (!cursor.isAnonymousRecordDecl()) break;
final mt = cursor.toCodeGenType(context);
if (mt.isIncompleteCompound) {
parsed.incompleteCompoundMember = true;
}
// Anonymous members are always unnamed. To avoid environment-
// dependent naming issues with the generated code, we explicitly
// use the empty string as spelling.
final spelling = '';
parsed.compound.members.add(
CompoundMember(
dartDoc: getCursorDocComment(
context,
cursor,
indent: nesting.length + commentPrefix.length,
),
originalName: spelling,
name: compoundConf.renameMember(decl, spelling),
type: mt,
),
);
break;
}
} catch (e, s) {
context.logger.severe(e);
context.logger.severe(s);
rethrow;
}
}