[cinterop] Fix type annotation for struct containing anonymous union … (#4289)

This commit is contained in:
Vladimir Ivanov
2021-05-18 14:34:56 +03:00
committed by GitHub
parent b01478746c
commit 5a0f113e6d
10 changed files with 591 additions and 165 deletions
@@ -26,15 +26,13 @@ private class StructDeclImpl(spelling: String, override val location: Location)
}
private class StructDefImpl(
size: Long, align: Int, decl: StructDecl,
override val kind: Kind
) : StructDef(
size, align, decl
) {
override val members = mutableListOf<StructMember>()
override val methods = mutableListOf<FunctionDecl>()
override val staticFields = mutableListOf<GlobalDecl>()
}
size: Long,
align: Int,
override val kind: Kind,
override val members: List<StructMember>,
override val methods: List<FunctionDecl>,
override val staticFields: List<GlobalDecl>
) : StructDef(size, align)
private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) {
override val constants = mutableListOf<EnumConstant>()
@@ -181,24 +179,21 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
): StructDecl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl ->
val definitionCursor = clang_getCursorDefinition(cursor)
if (clang_Cursor_isNull(definitionCursor) == 0) {
assert(clang_isCursorDefinition(definitionCursor) != 0)
// TODO: is this a bug or this is a wrong thing to do?
// Otherwise c++ class definition is created from its forward declaration
// and hence is empty.
//createStructDef(decl, cursor)
createStructDef(decl, definitionCursor)
decl.def = createStructDef(definitionCursor, definitionCursor.type, decl)
}
}
private fun createStructDecl(cursor: CValue<CXCursor>): StructDeclImpl {
val cursorType = clang_getCursorType(cursor)
val typeSpelling = clang_getTypeSpelling(cursorType).convertAndDispose()
return StructDeclImpl(typeSpelling, getLocation(cursor))
}
private fun createStructDecl(cursor: CValue<CXCursor>): StructDeclImpl =
StructDeclImpl(cursor.type.name, getLocation(cursor))
private fun addCxxMembers(classCursor: CValue<CXCursor>, clazz: StructDefImpl) {
if (library.language != Language.CPP) return
private data class CxxMembers(val methods: List<FunctionDecl> = emptyList(), val staticFields: List<GlobalDecl> = emptyList())
private fun collectCxxMembers(classCursor: CValue<CXCursor>, receiverType: RecordType): CxxMembers {
assert(library.language == Language.CPP)
val methods = mutableListOf<FunctionDecl>()
val staticFields = mutableListOf<GlobalDecl>()
// TODO skip method (function) when encounter UnsupportedType in params or ret value. Otherwise all class methods will be lost due to exception (?)
visitChildren(classCursor) { cursor, _ ->
@@ -206,23 +201,23 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
// TODO If a kotlin class is _conceptually_ derived from its c++ counterpart, then it shall be able to override virtual private and access protected
when (cursor.kind) {
CXCursorKind.CXCursor_CXXMethod -> {
val isOperatorFunction = (clang_getCursorSpelling(cursor).convertAndDispose().take(8) == "operator")
val isOperatorFunction = cursor.spelling.startsWith("operator")
// operators are Not Implemented Yet
if (!isOperatorFunction) {
if (clang_isFunctionTypeVariadic(clang_getCursorType(cursor)) == 0) // FIXME why it doesn't work???
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
getFunction(cursor, receiverType)?.let { methods.add(it) }
}
}
CXCursorKind.CXCursor_Constructor,
CXCursorKind.CXCursor_Destructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
getFunction(cursor, receiverType)?.let { methods.add(it) }
CXCursorKind.CXCursor_VarDecl -> {
clazz.staticFields.add(GlobalDecl(
name =getCursorSpelling(cursor),
staticFields.add(GlobalDecl(
name = getCursorSpelling(cursor),
type = convertCursorType(cursor),
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0,
parentName = clazz.decl.spelling)
parentName = receiverType.decl.spelling)
)
}
@@ -232,72 +227,90 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
}
CXChildVisitResult.CXChildVisit_Continue
}
return CxxMembers(methods, staticFields)
}
private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue<CXCursor>) {
private fun createStructDef(cursor: CValue<CXCursor>, structType: CValue<CXType>, structDecl: StructDecl? = null): StructDefImpl {
assert(clang_isCursorDefinition(cursor) != 0)
val type = clang_getCursorType(cursor)
val fields = mutableListOf<StructMember>()
addDeclaredFields(fields, type, type)
val size = clang_Type_getSizeOf(type)
val align = clang_Type_getAlignOf(type).toInt()
val members = getMembers(cursor, structType)
val cxxMembers =
if (library.language == Language.CPP && structDecl != null) collectCxxMembers(cursor, RecordType(structDecl))
else CxxMembers()
val structDef = StructDefImpl(
size, align, structDecl,
when (cursor.kind) {
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
else -> error(cursor.kind)
}
)
structDef.members += fields
addCxxMembers(cursor, structDef)
structDecl.def = structDef
with(cxxMembers) {
return StructDefImpl(
size, align,
when (cursor.kind) {
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
else -> error(cursor.kind)
},
members,
methods,
staticFields
)
}
}
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
// TODO: We don't exactly preserve C++ layout here, but
// we don't allow general case C++ classes by value at the moment.
getFields(containerType).filter { library.language != Language.CPP || it.isCxxPublic }.forEach { fieldCursor ->
val name = getCursorSpelling(fieldCursor)
if (name.isNotEmpty()) {
val fieldType = convertCursorType(fieldCursor)
val offset = clang_Type_getOffsetOf(structType, name)
val member = if (offset < 0) {
IncompleteField(name, fieldType)
} else if (clang_Cursor_isBitField(fieldCursor) == 0) {
val canonicalFieldType = clang_getCanonicalType(clang_getCursorType(fieldCursor))
Field(
name,
fieldType,
offset,
clang_Type_getSizeOf(canonicalFieldType),
clang_Type_getAlignOf(canonicalFieldType)
)
} else {
val size = clang_getFieldDeclBitWidth(fieldCursor)
BitField(name, fieldType, offset, size)
}
result.add(member)
} else {
// Unnamed field.
val fieldType = clang_getCursorType(fieldCursor)
when (fieldType.kind) {
CXTypeKind.CXType_Record -> {
// Unnamed struct fields also contribute their fields:
addDeclaredFields(result, structType, fieldType)
// cursor may be at the root struct or at a inner anonymous struct or union,
// while structType is always the nearest named enclosing struct/union (i.e. root struct)
// All offsets are calculated relative to this named parent
private fun getMembers(cursor: CValue<CXCursor>, structType: CValue<CXType>): List<StructMember> =
// TODO: We don't exactly preserve C++ layout here, but we don't allow general case C++ classes by value at the moment.
getFields(cursor.type).filter { library.language != Language.CPP || it.isCxxPublic }.map { fieldCursor ->
/*
* We want to identify anonymous struct/union member, according with definition (ISO/IEC 9899):
* "An unnamed member whose type specifier is a structure specifier with no tag is called an anonymous structure"
* `clang_Cursor_isAnonymous` intended to identify such entity and distinguish with cases alike:
* struct {
* struct {int x; } f; // named member of anonymous type "struct with no tag"
* int : 16; // anonymous bitfield
* typedef struct { int z; } foo; // c++ only; anon struct but the struct tag is implicitly assigned by the compiler; clang_getTypeSpelling still empty
* struct { int a; }; // this is the only one that we are looking for, i.e. anonymous struct member
* }
* `clang_Cursor_isAnonymous` implementation has been changed since LLVM 8 so we have to additionally check type.kind == CXType_Record
* Starting from LLVM 9 a new function `clang_Cursor_isAnonymousRecordDecl` provided specifically for that.
* Also, both `clang_Cursor_isAnonymous` and `clang_Cursor_isAnonymousRecordDecl` expect StructDecl cursor but we got
* FieldDecl cursor now, so we have to convert cursor to type declaration cursor first (e.g. StructDecl))
*/
val declCursor = clang_getTypeDeclaration(fieldCursor.type)
// Behavior of clang_Cursor_isAnonymous is changing starting from LLVM 8.
// Use lately introduced clang_Cursor_isAnonymousRecordDecl when available (LLVM 9)
val isAnonymousRecordType = (fieldCursor.type.kind == CXType_Record) && (clang_Cursor_isAnonymous(declCursor) == 1)
when {
isAnonymousRecordType -> {
// TODO: clang_Cursor_getOffsetOfField is OK for anonymous, but only for the 1st level of such nesting
AnonymousInnerRecord(
createStructDef(clang_getCursorDefinition(declCursor), structType))
}
else -> {
// Nothing.
val name = getCursorSpelling(fieldCursor)
val fieldType = convertCursorType(fieldCursor)
val offset = clang_Type_getOffsetOf(structType, name)
if (offset < 0) {
IncompleteField(name)
} else if (clang_Cursor_isBitField(fieldCursor) == 0) {
val canonicalFieldType = clang_getCanonicalType(fieldCursor.type)
Field(
name,
fieldType,
offset,
clang_Type_getSizeOf(canonicalFieldType),
clang_Type_getAlignOf(canonicalFieldType)
)
} else {
val size = clang_getFieldDeclBitWidth(fieldCursor)
BitField(name, fieldType, offset, size)
}
}
}
}
}
}
private fun getEnumDefAt(cursor: CValue<CXCursor>): EnumDefImpl {
if (clang_isCursorDefinition(cursor) == 0) {
@@ -1019,17 +1032,15 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
return getParentName(parent, nextPkg)
}
private fun getFunction(cursor: CValue<CXCursor>, receiver: StructDecl? = null): FunctionDecl? {
private fun getFunction(cursor: CValue<CXCursor>, receiver: RecordType? = null): FunctionDecl? {
if (!isFuncDeclEligible(cursor)) {
log("Skip function ${clang_getCursorSpelling(cursor).convertAndDispose()}")
return null
}
var name = clang_getCursorSpelling(cursor).convertAndDispose()
var name = cursor.spelling
val cursorReturnType = clang_getCursorResultType(cursor)
val cursorReturnTypeSpelling = clang_getTypeSpelling(cursorReturnType).convertAndDispose()
if (cursorReturnTypeSpelling.isUnknownTemplate()) return null
if (cursorReturnType.name.isUnknownTemplate()) return null
var returnType = convertType(cursorReturnType, clang_getCursorResultTypeAttributes(cursor))
@@ -1048,20 +1059,20 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
// TODO Do the following if clang_getCursorLanguage(cursor) == CXLanguageKind.CXLanguage_CPlusPlus ...
val parentName = getParentName(cursor)
val cxxMethodInfo = receiver?.let {
val receiverPointerType = PointerType(receiver, clang_CXXMethod_isConst(cursor) != 0)
CxxMethodInfo(
PointerType(RecordType(receiver),
clang_CXXMethod_isConst(cursor) != 0), // CXCursor_ConversionFunction has constness too
receiverPointerType, // CXCursor_ConversionFunction has constness too
when (cursor.kind) {
CXCursorKind.CXCursor_Constructor -> {
returnType = PointerType(RecordType(receiver))
returnType = receiverPointerType
name = "__init__" // It is intended to init preallocated memory with placement new, so it is not "create" factory method. TODO One may want "create" method also.
// Parameter type for placement new is void*, but I want to emphasize that memory block ahall have proper size and alignment
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
parameters.add(0, Parameter("self", receiverPointerType, false))
CxxMethodKind.Constructor
}
CXCursorKind.CXCursor_Destructor -> {
name = "__destroy__"
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
parameters.add(0, Parameter("self", receiverPointerType, false))
CxxMethodKind.Destructor
}
// CXCursorKind.CXCursor_ConversionFunction -> ...
@@ -1069,9 +1080,7 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
if (clang_CXXMethod_isStatic(cursor) != 0) {
CxxMethodKind.StaticMethod
} else {
parameters.add(0, Parameter("self",
PointerType(RecordType(receiver), clang_CXXMethod_isConst(cursor) != 0),
false))
parameters.add(0, Parameter("self", receiverPointerType, false))
CxxMethodKind.InstanceMethod
}
else -> CxxMethodKind.None // Not implemented. Not expected, OK to assert (?)
@@ -1148,7 +1157,7 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole
val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0..argNum - 1).map {
val argCursor = clang_Cursor_getArgument(cursor, it)
if (clang_getTypeSpelling(clang_getCursorType(argCursor)).convertAndDispose().isUnknownTemplate()) {
if (argCursor.type.name.isUnknownTemplate()) {
return null
}
val argName = getCursorSpelling(argCursor)
@@ -120,25 +120,30 @@ interface TypeDeclaration {
val location: Location
}
sealed class StructMember(val name: String, val type: Type) {
sealed class StructMember(val name: String) {
abstract val offset: Long?
}
/**
* C struct field.
*/
class Field(name: String, type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
: StructMember(name, type)
class Field(name: String, val type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
: StructMember(name)
val Field.isAligned: Boolean
get() = offset % (typeAlign * 8) == 0L
class BitField(name: String, type: Type, override val offset: Long, val size: Int) : StructMember(name, type)
class BitField(name: String, val type: Type, override val offset: Long, val size: Int) : StructMember(name)
class IncompleteField(name: String, type: Type) : StructMember(name, type) {
class IncompleteField(name: String) : StructMember(name) {
override val offset: Long? get() = null
}
class AnonymousInnerRecord(val def: StructDef) : StructMember("") {
override val offset: Long? get() = null
val typeSize: Long = def.size
}
/**
* C struct declaration.
*/
@@ -153,19 +158,36 @@ abstract class StructDecl(val spelling: String) : TypeDeclaration {
* @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`.
* May be `false` even if the struct has natural layout.
*/
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) {
abstract class StructDef(val size: Long, val align: Int) {
enum class Kind {
STRUCT, UNION, CLASS
}
abstract val methods: List<FunctionDecl>
abstract val members: List<StructMember>
abstract val staticFields: List<GlobalDecl>
abstract val kind: Kind
abstract val members: List<StructMember>
abstract val methods: List<FunctionDecl>
abstract val staticFields: List<GlobalDecl>
val fields: List<Field> get() = members.filterIsInstance<Field>()
val bitFields: List<BitField> get() = members.filterIsInstance<BitField>()
val fields: List<Field>
get() = mutableListOf<Field>().apply {
members.forEach {
when (it) {
is Field -> add(it)
is AnonymousInnerRecord -> addAll(it.def.fields)
}
}
}
val bitFields: List<BitField>
get() = mutableListOf<BitField>().apply {
members.forEach {
when (it) {
is BitField -> add(it)
is AnonymousInnerRecord -> addAll(it.def.bitFields)
}
}
}
}
/**