[ffigen] Skip methods that have incomplete types (#412)
* Skip methods that have incomplete types
Also change how method return types are parsed
* Fix overly generic method return types
* Fix tests
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index 04eb6e5..b33e97b 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -125,7 +125,7 @@
for (final m in methods.values) {
final methodName = m._getDartMethodName(uniqueNamer);
final isStatic = m.isClass;
- final returnType = m.returnType!;
+ final returnType = m.returnType;
// The method declaration.
if (m.dartDoc != null) {
@@ -397,7 +397,7 @@
final String? dartDoc;
final String originalName;
final ObjCProperty? property;
- Type? returnType;
+ final Type returnType;
final bool isNullableReturn;
final List<ObjCMethodParam> params;
final ObjCMethodKind kind;
@@ -412,7 +412,7 @@
this.dartDoc,
required this.kind,
required this.isClass,
- this.returnType,
+ required this.returnType,
this.isNullableReturn = false,
List<ObjCMethodParam>? params_,
}) : params = params_ ?? [];
@@ -423,14 +423,13 @@
void addDependencies(
Set<Binding> dependencies, ObjCBuiltInFunctions builtInFunctions) {
- returnType ??= NativeType(SupportedNativeType.Void);
- returnType!.addDependencies(dependencies);
+ returnType.addDependencies(dependencies);
for (final p in params) {
p.type.addDependencies(dependencies);
}
selObject ??= builtInFunctions.getSelObject(originalName)
..addDependencies(dependencies);
- msgSend ??= builtInFunctions.getMsgSendFunc(returnType!, params)
+ msgSend ??= builtInFunctions.getMsgSendFunc(returnType, params)
..addDependencies(dependencies);
}
diff --git a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
index 58108aa..e8755f5 100644
--- a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -791,6 +791,23 @@
_clang_Type_getObjCObjectBaseTypePtr
.asFunction<CXType Function(CXType)>();
+ /// Retrieve the return type associated with a given cursor.
+ ///
+ /// This only returns a valid type if the cursor refers to a function or method.
+ CXType clang_getCursorResultType(
+ CXCursor C,
+ ) {
+ return _clang_getCursorResultType(
+ C,
+ );
+ }
+
+ late final _clang_getCursorResultTypePtr =
+ _lookup<ffi.NativeFunction<CXType Function(CXCursor)>>(
+ 'clang_getCursorResultType');
+ late final _clang_getCursorResultType =
+ _clang_getCursorResultTypePtr.asFunction<CXType Function(CXCursor)>();
+
/// Return the number of elements of an array or vector type.
///
/// If a type is passed in that is not an array or vector type,
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
index bf9d038..f2e4fc9 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
@@ -178,8 +178,8 @@
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertySetter,
- isClass: isClass);
- setter.returnType = NativeType(SupportedNativeType.Void);
+ isClass: isClass,
+ returnType: NativeType(SupportedNativeType.Void));
setter.params
.add(ObjCMethodParam(fieldType, 'value', isNullable: isNullable));
itf.addMethod(setter);
@@ -190,11 +190,19 @@
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: '
@@ -215,10 +223,6 @@
int _parseMethodVisitor(clang_types.CXCursor cursor,
clang_types.CXCursor parent, Pointer<Void> clientData) {
switch (cursor.kind) {
- case clang_types.CXCursorKind.CXCursor_TypeRef:
- case clang_types.CXCursorKind.CXCursor_ObjCClassRef:
- _parseMethodReturnType(cursor);
- break;
case clang_types.CXCursorKind.CXCursor_ParmDecl:
_parseMethodParam(cursor);
break;
@@ -230,22 +234,6 @@
return clang_types.CXChildVisitResult.CXChildVisit_Continue;
}
-void _parseMethodReturnType(clang_types.CXCursor cursor) {
- final parsed = _methodStack.top;
- if (parsed.method.returnType != null) {
- parsed.hasError = true;
- _logger.fine(
- ' >> Extra return type: ${cursor.completeStringRepr()}');
- _logger.warning('Method "${parsed.method.originalName}" in instance '
- '"${_interfaceStack.top.interface.originalName}" has multiple return '
- 'types.');
- } else {
- parsed.method.returnType = cursor.type().toCodeGenType();
- _logger.fine(' >> Return type: '
- '${parsed.method.returnType} ${cursor.completeStringRepr()}');
- }
-}
-
void _parseMethodParam(clang_types.CXCursor cursor) {
/*
TODO(#334): Change this to use:
@@ -260,14 +248,21 @@
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()}');
- _methodStack.top.method.params
- .add(ObjCMethodParam(type, name, isNullable: isNullable));
+ parsed.method.params.add(ObjCMethodParam(type, name, isNullable: isNullable));
}
void _markMethodReturnsRetained(clang_types.CXCursor cursor) {
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
index 1a69715..31420cd 100644
--- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
+++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -45,6 +45,13 @@
if (config.language == Language.objc) {
switch (cxtype.kind) {
case clang_types.CXTypeKind.CXType_ObjCObjectPointer:
+ final pt = clang.clang_getPointeeType(cxtype);
+ final s = getCodeGenType(pt,
+ ignoreFilter: ignoreFilter, pointerReference: true);
+ if (s is ObjCInterface) {
+ return s;
+ }
+ return PointerType(objCObjectType);
case clang_types.CXTypeKind.CXType_ObjCId:
case clang_types.CXTypeKind.CXType_ObjCTypeParam:
case clang_types.CXTypeKind.CXType_ObjCClass:
diff --git a/pkgs/ffigen/test/native_objc_test/bad_method_config.yaml b/pkgs/ffigen/test/native_objc_test/bad_method_config.yaml
new file mode 100644
index 0000000..d26ff79
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/bad_method_config.yaml
@@ -0,0 +1,23 @@
+# 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.
+
+# =================== GENERATING TEST BINDINGS ==================
+# dart run ffigen --config test/bad_method_test/config.yaml
+# ===============================================================
+
+name: NativeObjCLibrary
+description: 'Native Objective C test'
+language: objc
+output: 'test/native_objc_test/bad_method_test_bindings.dart'
+objc-interfaces:
+ include:
+ - 'BadMethodTestObject'
+functions:
+ exclude:
+ - '.*'
+headers:
+ entry-points:
+ - 'test/native_objc_test/bad_method_test.m'
+preamble: |
+ // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/pkgs/ffigen/test/native_objc_test/bad_method_test.dart b/pkgs/ffigen/test/native_objc_test/bad_method_test.dart
new file mode 100644
index 0000000..625cc2b
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/bad_method_test.dart
@@ -0,0 +1,34 @@
+// 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.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'bad_method_test_bindings.dart';
+import 'util.dart';
+
+void main() {
+ late NativeObjCLibrary lib;
+ group('bad_method_test', () {
+ setUpAll(() {
+ logWarnings();
+ final dylib = File('test/native_objc_test/bad_method_test.dylib');
+ verifySetupFile(dylib);
+ lib = NativeObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
+ generateBindingsForCoverage('bad_method');
+ });
+
+ test("Test methods that weren't skipped", () {
+ final obj = BadMethodTestObject.new1(lib);
+ final structPtr = obj.incompletePointerReturn();
+ expect(structPtr.address, 1234);
+ expect(obj.incompletePointerParam_(structPtr), 1234);
+ });
+ });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/bad_method_test.m b/pkgs/ffigen/test/native_objc_test/bad_method_test.m
new file mode 100644
index 0000000..132daf8
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/bad_method_test.m
@@ -0,0 +1,29 @@
+// 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 <Foundation/NSObject.h>
+
+struct IncompleteStruct;
+
+@interface BadMethodTestObject : NSObject {
+}
+
+- (struct IncompleteStruct)incompleteReturn; // Skipped.
+- (struct IncompleteStruct*)incompletePointerReturn; // Not skipped.
+- (int64_t)incompleteParam:(struct IncompleteStruct)x; // Skipped.
+- (int64_t)incompletePointerParam:(struct IncompleteStruct*)x; // Not skipped.
+
+@end
+
+@implementation BadMethodTestObject
+
+- (struct IncompleteStruct*)incompletePointerReturn {
+ return (struct IncompleteStruct*)1234;
+}
+
+- (int64_t)incompletePointerParam:(struct IncompleteStruct*)x {
+ return (int64_t)x;
+}
+
+@end
diff --git a/pkgs/ffigen/test/native_objc_test/setup.dart b/pkgs/ffigen/test/native_objc_test/setup.dart
index 22d99c9..78c7427 100644
--- a/pkgs/ffigen/test/native_objc_test/setup.dart
+++ b/pkgs/ffigen/test/native_objc_test/setup.dart
@@ -47,6 +47,7 @@
const testNames = [
'automated_ref_count',
+ 'bad_method',
'block',
'cast',
'category',
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml
index ad0bfd0..3087cf8 100644
--- a/pkgs/ffigen/tool/libclang_config.yaml
+++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -90,6 +90,7 @@
- clang_Cursor_getArgument
- clang_getNumArgTypes
- clang_getArgType
+ - clang_getCursorResultType
- clang_getEnumConstantDeclValue
- clang_equalRanges
- clang_Cursor_getCommentRange