blob: bace1c4e809e64949fd701303db3304b7ce9885b [file] [log] [blame]
// Copyright (c) 2022, 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/header_parser/data.dart';
import 'package:logging/logging.dart';
import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../includer.dart';
import '../utils.dart';
final _logger = Logger('ffigen.header_parser.objcinterfacedecl_parser');
class _ParsedObjCInterface {
ObjCInterface interface;
_ParsedObjCInterface(this.interface);
}
class _ParsedObjCMethod {
ObjCMethod method;
bool hasError = false;
_ParsedObjCMethod(this.method);
}
final _interfaceStack = Stack<_ParsedObjCInterface>();
final _methodStack = Stack<_ParsedObjCMethod>();
Type? parseObjCInterfaceDeclaration(
clang_types.CXCursor cursor, {
/// Option to ignore declaration filter (Useful in case of extracting
/// declarations when they are passed/returned by an included function.)
bool ignoreFilter = false,
}) {
final itfUsr = cursor.usr();
final itfName = cursor.spelling();
if (!ignoreFilter && !shouldIncludeObjCInterface(itfUsr, itfName)) {
return null;
}
final t = cursor.type();
final name = t.spelling();
_logger.fine('++++ Adding ObjC interface: '
'Name: $name, ${cursor.completeStringRepr()}');
return ObjCInterface(
usr: itfUsr,
originalName: name,
name: config.objcInterfaces.renameUsingConfig(name),
lookupName: config.objcModulePrefixer.applyPrefix(name),
dartDoc: getCursorDocComment(cursor),
builtInFunctions: objCBuiltInFunctions,
isBuiltIn: cursor.isInSystemHeader(),
);
}
void fillObjCInterfaceMethodsIfNeeded(
ObjCInterface itf, clang_types.CXCursor cursor) {
if (_isClassDeclaration(cursor)) {
// @class declarations are ObjC's way of forward declaring classes. In that
// case there's nothing to fill yet.
return;
}
if (itf.filled) return;
itf.filled = true; // Break cycles.
_logger.fine('++++ Filling ObjC interface: '
'Name: ${itf.originalName}, ${cursor.completeStringRepr()}');
_interfaceStack.push(_ParsedObjCInterface(itf));
clang.clang_visitChildren(
cursor,
Pointer.fromFunction(_parseInterfaceVisitor, exceptional_visitor_return),
nullptr);
_interfaceStack.pop();
_logger.fine('++++ Finished ObjC interface: '
'Name: ${itf.originalName}, ${cursor.completeStringRepr()}');
}
bool _isClassDeclarationResult = false;
bool _isClassDeclaration(clang_types.CXCursor cursor) {
// It's a class declaration if it has no children other than ObjCClassRef.
_isClassDeclarationResult = true;
clang.clang_visitChildren(
cursor,
Pointer.fromFunction(
_isClassDeclarationVisitor, exceptional_visitor_return),
nullptr);
return _isClassDeclarationResult;
}
int _isClassDeclarationVisitor(clang_types.CXCursor cursor,
clang_types.CXCursor parent, Pointer<Void> clientData) {
if (cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassRef) {
return clang_types.CXChildVisitResult.CXChildVisit_Continue;
}
_isClassDeclarationResult = false;
return clang_types.CXChildVisitResult.CXChildVisit_Break;
}
int _parseInterfaceVisitor(clang_types.CXCursor cursor,
clang_types.CXCursor parent, Pointer<Void> clientData) {
switch (cursor.kind) {
case clang_types.CXCursorKind.CXCursor_ObjCSuperClassRef:
_parseSuperType(cursor);
break;
case clang_types.CXCursorKind.CXCursor_ObjCPropertyDecl:
_parseProperty(cursor);
break;
case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl:
case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl:
_parseMethod(cursor);
break;
}
return clang_types.CXChildVisitResult.CXChildVisit_Continue;
}
void _parseSuperType(clang_types.CXCursor cursor) {
final superType = cursor.type().toCodeGenType();
_logger.fine(' > Super type: '
'$superType ${cursor.completeStringRepr()}');
final itf = _interfaceStack.top.interface;
if (superType is ObjCInterface) {
itf.superType = superType;
} else {
_logger.severe(
'Super type of $itf is $superType, which is not a valid interface.');
}
}
void _parseProperty(clang_types.CXCursor cursor) {
final itf = _interfaceStack.top.interface;
final fieldName = cursor.spelling();
final fieldType = cursor.type().toCodeGenType();
if (fieldType.isIncompleteCompound) {
_logger.warning('Property "$fieldName" in instance "${itf.originalName}" '
'has incomplete type: $fieldType.');
return;
}
final dartDoc = getCursorDocComment(cursor);
final propertyAttributes =
clang.clang_Cursor_getObjCPropertyAttributes(cursor, 0);
final isClass = propertyAttributes &
clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_class >
0;
final isReadOnly = propertyAttributes &
clang_types.CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly >
0;
// TODO(#334): Use the nullable attribute to decide this.
final isNullable =
cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
final property = ObjCProperty(fieldName);
_logger.fine(' > Property: '
'$fieldType $fieldName ${cursor.completeStringRepr()}');
final getterName =
clang.clang_Cursor_getObjCPropertyGetterName(cursor).toStringAndDispose();
final getter = ObjCMethod(
originalName: getterName,
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertyGetter,
isClass: isClass,
returnType: fieldType,
isNullableReturn: isNullable,
);
itf.addMethod(getter);
if (!isReadOnly) {
final setterName = clang
.clang_Cursor_getObjCPropertySetterName(cursor)
.toStringAndDispose();
final setter = ObjCMethod(
originalName: setterName,
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertySetter,
isClass: isClass,
returnType: NativeType(SupportedNativeType.Void));
setter.params
.add(ObjCMethodParam(fieldType, 'value', isNullable: isNullable));
itf.addMethod(setter);
}
}
void _parseMethod(clang_types.CXCursor cursor) {
final methodName = cursor.spelling();
final isClassMethod =
cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
final returnType = clang.clang_getCursorResultType(cursor).toCodeGenType();
if (returnType.isIncompleteCompound) {
_logger.warning('Method "$methodName" in instance '
'"${_interfaceStack.top.interface.originalName}" has incomplete '
'return type: $returnType.');
return;
}
final method = ObjCMethod(
originalName: methodName,
dartDoc: getCursorDocComment(cursor),
kind: ObjCMethodKind.method,
isClass: isClassMethod,
returnType: returnType,
);
final parsed = _ParsedObjCMethod(method);
_logger.fine(' > ${isClassMethod ? 'Class' : 'Instance'} method: '
'${method.originalName} ${cursor.completeStringRepr()}');
_methodStack.push(parsed);
clang.clang_visitChildren(
cursor,
Pointer.fromFunction(_parseMethodVisitor, exceptional_visitor_return),
nullptr);
_methodStack.pop();
if (parsed.hasError) {
// Discard it.
return;
}
_interfaceStack.top.interface.addMethod(method);
}
int _parseMethodVisitor(clang_types.CXCursor cursor,
clang_types.CXCursor parent, Pointer<Void> clientData) {
switch (cursor.kind) {
case clang_types.CXCursorKind.CXCursor_ParmDecl:
_parseMethodParam(cursor);
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsRetained:
_markMethodReturnsRetained(cursor);
break;
default:
}
return clang_types.CXChildVisitResult.CXChildVisit_Continue;
}
void _parseMethodParam(clang_types.CXCursor cursor) {
/*
TODO(#334): Change this to use:
clang.clang_Type_getNullability(cursor.type()) ==
clang_types.CXTypeNullabilityKind.CXTypeNullability_Nullable;
NOTE: This will only work with the
clang_types
.CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes
option set.
*/
final parsed = _methodStack.top;
final isNullable =
cursor.type().kind == clang_types.CXTypeKind.CXType_ObjCObjectPointer;
final name = cursor.spelling();
final type = cursor.type().toCodeGenType();
if (type.isIncompleteCompound) {
parsed.hasError = true;
_logger.warning('Method "${parsed.method.originalName}" in instance '
'"${_interfaceStack.top.interface.originalName}" has incomplete '
'parameter type: $type.');
return;
}
_logger.fine(
' >> Parameter: $type $name ${cursor.completeStringRepr()}');
parsed.method.params.add(ObjCMethodParam(type, name, isNullable: isNullable));
}
void _markMethodReturnsRetained(clang_types.CXCursor cursor) {
_methodStack.top.method.returnsRetained = true;
}
BindingType? parseObjCCategoryDeclaration(clang_types.CXCursor cursor) {
// Categories add methods to an existing interface, so first we run a visitor
// to find the interface, then we fully parse that interface, then we run the
// _parseInterfaceVisitor over the category to add its methods etc. Reusing
// the interface visitor relies on the fact that the structure of the category
// AST looks exactly the same as the interface AST, and that the category's
// interface is a different kind of node to the interface's super type (so is
// ignored by _parseInterfaceVisitor).
final name = cursor.spelling();
_logger.fine('++++ Adding ObjC category: '
'Name: $name, ${cursor.completeStringRepr()}');
_findCategoryInterfaceVisitorResult = null;
clang.clang_visitChildren(
cursor,
Pointer.fromFunction(
_findCategoryInterfaceVisitor, exceptional_visitor_return),
nullptr);
final itfCursor = _findCategoryInterfaceVisitorResult;
if (itfCursor == null) {
_logger.severe('Category $name has no interface.');
return null;
}
// TODO(#347): Currently any interface with a category bypasses the filters.
final itf = itfCursor.type().toCodeGenType();
if (itf is! ObjCInterface) {
_logger.severe(
'Interface of category $name is $itf, which is not a valid interface.');
return null;
}
_interfaceStack.push(_ParsedObjCInterface(itf));
clang.clang_visitChildren(
cursor,
Pointer.fromFunction(_parseInterfaceVisitor, exceptional_visitor_return),
nullptr);
_interfaceStack.pop();
_logger.fine('++++ Finished ObjC category: '
'Name: $name, ${cursor.completeStringRepr()}');
return itf;
}
clang_types.CXCursor? _findCategoryInterfaceVisitorResult;
int _findCategoryInterfaceVisitor(clang_types.CXCursor cursor,
clang_types.CXCursor parent, Pointer<Void> clientData) {
if (cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassRef) {
_findCategoryInterfaceVisitorResult = cursor;
return clang_types.CXChildVisitResult.CXChildVisit_Break;
}
return clang_types.CXChildVisitResult.CXChildVisit_Continue;
}