| // 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'); |
| |
| Pointer< |
| NativeFunction< |
| Int32 Function( |
| clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>? |
| _parseInterfaceVisitorPtr; |
| Pointer< |
| NativeFunction< |
| Int32 Function( |
| clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>? |
| _isClassDeclarationVisitorPtr; |
| Pointer< |
| NativeFunction< |
| Int32 Function( |
| clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>? |
| _parseMethodVisitorPtr; |
| Pointer< |
| NativeFunction< |
| Int32 Function( |
| clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>? |
| _findCategoryInterfaceVisitorPtr; |
| |
| 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, |
| ); |
| } |
| |
| 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, |
| _parseInterfaceVisitorPtr ??= 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, |
| _isClassDeclarationVisitorPtr ??= 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, |
| _parseMethodVisitorPtr ??= |
| 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, |
| _findCategoryInterfaceVisitorPtr ??= 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, |
| _parseInterfaceVisitorPtr ??= 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; |
| } |