[ffigen] ObjC static functions (#633)
* WIP static functions
* Revert old code gen changes
* Refactor function code gen
* Arg and return conversions
* fix bugs
* Fix analysis, add more tests
* fmt
* Fix autorelease pool test
* Handle NS_RETURNS_RETAINED
* Daco's comments
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index 3de820d..a1aef8c 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -9,6 +9,7 @@
- When generating typedefs for `Pointer<NativeFunction<Function>>`, also
generate a typedef for the `Function`.
- Use Dart wrapper types in args and returns of ObjCBlocks.
+- Use Dart wrapper types in args and returns of static functions.
- Bump min SDK version to 3.2.0-114.0.dev.
- Renamed `asset` to `assetId` for `ffi-native`.
diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart
index cbdebe7..766cc28 100644
--- a/pkgs/ffigen/lib/src/code_generator/func.dart
+++ b/pkgs/ffigen/lib/src/code_generator/func.dart
@@ -42,6 +42,7 @@
final bool exposeSymbolAddress;
final bool exposeFunctionTypedefs;
final bool isLeaf;
+ final bool objCReturnsRetained;
final FfiNativeConfig ffiNativeConfig;
late final String funcPointerName;
@@ -61,6 +62,7 @@
this.exposeSymbolAddress = false,
this.exposeFunctionTypedefs = false,
this.isLeaf = false,
+ this.objCReturnsRetained = false,
super.isInternal,
this.ffiNativeConfig = const FfiNativeConfig(enabled: false),
}) : functionType = FunctionType(
@@ -93,8 +95,6 @@
BindingString toBindingString(Writer w) {
final s = StringBuffer();
final enclosingFuncName = name;
- final funcVarName = w.wrapperLevelUniqueNamer.makeUnique('_$name');
- funcPointerName = w.wrapperLevelUniqueNamer.makeUnique('_${name}Ptr');
if (dartDoc != null) {
s.write(makeDartDoc(dartDoc!));
@@ -109,37 +109,71 @@
functionType.getCType(w, writeArgumentNames: false);
final dartType = _exposedFunctionTypealias?.getFfiDartType(w) ??
functionType.getFfiDartType(w, writeArgumentNames: false);
+ final needsWrapper = !functionType.sameDartAndFfiDartType && !isInternal;
+
+ final isLeafString = isLeaf ? 'isLeaf:true' : '';
+ final funcVarName = w.wrapperLevelUniqueNamer.makeUnique('_$name');
+ final ffiReturnType = functionType.returnType.getFfiDartType(w);
+ final ffiArgDeclString = functionType.dartTypeParameters
+ .map((p) => '${p.type.getFfiDartType(w)} ${p.name},\n')
+ .join('');
+
+ late final String dartReturnType;
+ late final String dartArgDeclString;
+ late final String funcImplCall;
+ if (needsWrapper) {
+ dartReturnType = functionType.returnType.getDartType(w);
+ dartArgDeclString = functionType.dartTypeParameters
+ .map((p) => '${p.type.getDartType(w)} ${p.name},\n')
+ .join('');
+
+ final argString = functionType.dartTypeParameters
+ .map((p) =>
+ '${p.type.convertDartTypeToFfiDartType(w, p.name, objCRetain: false)},\n')
+ .join('');
+ funcImplCall = functionType.returnType.convertFfiDartTypeToDartType(
+ w,
+ '$funcVarName($argString)',
+ ffiNativeConfig.enabled ? 'lib' : 'this',
+ objCRetain: !objCReturnsRetained,
+ );
+ } else {
+ dartReturnType = ffiReturnType;
+ dartArgDeclString = ffiArgDeclString;
+ final argString =
+ functionType.dartTypeParameters.map((p) => '${p.name},\n').join('');
+ funcImplCall = '$funcVarName($argString)';
+ }
if (ffiNativeConfig.enabled) {
final assetString = ffiNativeConfig.assetId != null
? ", assetId: '${ffiNativeConfig.assetId}'"
: '';
- final isLeafString = isLeaf ? ', isLeaf: true' : '';
- s.write(
- "@${w.ffiLibraryPrefix}.Native<$cType>(symbol: '$originalName'$assetString$isLeafString)\n");
+ final nativeFuncName = needsWrapper ? funcVarName : enclosingFuncName;
+ s.write('''
+@${w.ffiLibraryPrefix}.Native<$cType>(symbol: '$originalName'$assetString$isLeafString)
+external $ffiReturnType $nativeFuncName($ffiArgDeclString);
- s.write(
- 'external ${functionType.returnType.getFfiDartType(w)} $enclosingFuncName(\n');
- for (final p in functionType.dartTypeParameters) {
- s.write(' ${p.type.getFfiDartType(w)} ${p.name},\n');
+''');
+ if (needsWrapper) {
+ final libArg = functionType.returnType.sameDartAndFfiDartType
+ ? ''
+ : '${w.className} lib, ';
+ s.write('''
+$dartReturnType $enclosingFuncName($libArg$dartArgDeclString) => $funcImplCall;
+
+''');
}
- s.write(');\n\n');
} else {
- // Write enclosing function.
- s.write(
- '${functionType.returnType.getFfiDartType(w)} $enclosingFuncName(\n');
- for (final p in functionType.dartTypeParameters) {
- s.write(' ${p.type.getFfiDartType(w)} ${p.name},\n');
- }
- s.write(') {\n');
- s.write('return $funcVarName');
+ funcPointerName = w.wrapperLevelUniqueNamer.makeUnique('_${name}Ptr');
- s.write('(\n');
- for (final p in functionType.dartTypeParameters) {
- s.write(' ${p.name},\n');
- }
- s.write(' );\n');
- s.write('}\n');
+ // Write enclosing function.
+ s.write('''
+$dartReturnType $enclosingFuncName($dartArgDeclString) {
+ return $funcImplCall;
+}
+
+''');
if (exposeSymbolAddress) {
// Add to SymbolAddress in writer.
@@ -150,12 +184,14 @@
ptrName: funcPointerName,
);
}
+
// Write function pointer.
- s.write(
- "late final $funcPointerName = ${w.lookupFuncIdentifier}<${w.ffiLibraryPrefix}.NativeFunction<$cType>>('$originalName');\n");
- final isLeafString = isLeaf ? 'isLeaf:true' : '';
- s.write(
- 'late final $funcVarName = $funcPointerName.asFunction<$dartType>($isLeafString);\n\n');
+ s.write('''
+late final $funcPointerName = ${w.lookupFuncIdentifier}<
+ ${w.ffiLibraryPrefix}.NativeFunction<$cType>>('$originalName');
+late final $funcVarName = $funcPointerName.asFunction<$dartType>($isLeafString);
+
+''');
}
return BindingString(type: BindingStringType.func, string: s.toString());
diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart
index 1e734bc..49dcf60 100644
--- a/pkgs/ffigen/lib/src/code_generator/func_type.dart
+++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart
@@ -79,6 +79,11 @@
dartTypeParameters.every((p) => p.type.sameDartAndCType);
@override
+ bool get sameDartAndFfiDartType =>
+ returnType.sameDartAndFfiDartType &&
+ dartTypeParameters.every((p) => p.type.sameDartAndFfiDartType);
+
+ @override
String toString() => _getTypeImpl(false, (Type t) => t.toString());
@override
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
index b61efc7..d16ab11 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart
@@ -229,6 +229,9 @@
bool get sameDartAndCType => false;
@override
+ bool get sameDartAndFfiDartType => false;
+
+ @override
String convertDartTypeToFfiDartType(
Writer w,
String value, {
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index 2ed1f10..495cdf1 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -402,6 +402,9 @@
bool get sameDartAndCType => false;
@override
+ bool get sameDartAndFfiDartType => false;
+
+ @override
String convertDartTypeToFfiDartType(
Writer w,
String value, {
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart
index a8d8c52..f3c0b87 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_nullable.dart
@@ -45,6 +45,9 @@
bool get sameDartAndCType => false;
@override
+ bool get sameDartAndFfiDartType => false;
+
+ @override
String convertDartTypeToFfiDartType(
Writer w,
String value, {
diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart
index dcaf597..30e2e6b 100644
--- a/pkgs/ffigen/lib/src/code_generator/pointer.dart
+++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart
@@ -88,6 +88,9 @@
bool get sameDartAndCType => false;
@override
+ bool get sameDartAndFfiDartType => false;
+
+ @override
String convertDartTypeToFfiDartType(
Writer w,
String value, {
diff --git a/pkgs/ffigen/lib/src/code_generator/type.dart b/pkgs/ffigen/lib/src/code_generator/type.dart
index fff7f0f..c1d1e22 100644
--- a/pkgs/ffigen/lib/src/code_generator/type.dart
+++ b/pkgs/ffigen/lib/src/code_generator/type.dart
@@ -54,6 +54,9 @@
/// Returns whether the dart type and C type string are same.
bool get sameDartAndCType => sameFfiDartAndCType;
+ /// Returns whether the dart type and FFI dart type string are same.
+ bool get sameDartAndFfiDartType => true;
+
/// Returns generated Dart code that converts the given value from its
/// DartType to its FfiDartType.
///
@@ -139,6 +142,9 @@
bool get sameDartAndCType => sameFfiDartAndCType;
@override
+ bool get sameDartAndFfiDartType => true;
+
+ @override
String convertDartTypeToFfiDartType(
Writer w,
String value, {
diff --git a/pkgs/ffigen/lib/src/code_generator/typealias.dart b/pkgs/ffigen/lib/src/code_generator/typealias.dart
index fe971a5..e31d148 100644
--- a/pkgs/ffigen/lib/src/code_generator/typealias.dart
+++ b/pkgs/ffigen/lib/src/code_generator/typealias.dart
@@ -159,6 +159,9 @@
bool get sameDartAndCType => type.sameDartAndCType;
@override
+ bool get sameDartAndFfiDartType => type.sameDartAndFfiDartType;
+
+ @override
String convertDartTypeToFfiDartType(
Writer w,
String value, {
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
index f9d1121..c2291b4 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -2,6 +2,8 @@
// 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/config_provider/config_types.dart';
import 'package:ffigen/src/header_parser/data.dart';
@@ -20,6 +22,7 @@
List<Func> funcs = [];
bool incompleteStructParameter = false;
bool unimplementedParameterType = false;
+ bool objCReturnsRetained = false;
_ParserFunc();
}
@@ -69,6 +72,13 @@
return _stack.pop().funcs;
}
+ // Look for any annotations on the function.
+ clang.clang_visitChildren(
+ cursor,
+ _parseAnnotationVisitorPtr ??= Pointer.fromFunction(
+ _parseAnnotationVisitor, exceptional_visitor_return),
+ nullptr);
+
// Initialized with a single value with no prefix and empty var args.
var varArgFunctions = [VarArgFunction('', [])];
if (config.varArgFunctions.containsKey(funcName)) {
@@ -97,6 +107,7 @@
exposeFunctionTypedefs:
config.exposeFunctionTypedefs.shouldInclude(funcName),
isLeaf: config.leafFunctions.shouldInclude(funcName),
+ objCReturnsRetained: _stack.top.objCReturnsRetained,
ffiNativeConfig: config.ffiNativeConfig,
));
}
@@ -147,3 +158,19 @@
Type _getParameterType(clang_types.CXCursor cursor) {
return cursor.toCodeGenType();
}
+
+Pointer<
+ NativeFunction<
+ Int32 Function(
+ clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
+ _parseAnnotationVisitorPtr;
+int _parseAnnotationVisitor(clang_types.CXCursor cursor,
+ clang_types.CXCursor parent, Pointer<Void> clientData) {
+ switch (cursor.kind) {
+ case clang_types.CXCursorKind.CXCursor_NSReturnsRetained:
+ _stack.top.objCReturnsRetained = true;
+ break;
+ default:
+ }
+ return clang_types.CXChildVisitResult.CXChildVisit_Continue;
+}
diff --git a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m b/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m
index d249389..cf8ab3a 100644
--- a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m
+++ b/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m
@@ -89,12 +89,14 @@
@end
-id createAutoreleasePool() {
- return [NSAutoreleasePool new];
+// Pass around the NSAutoreleasePool as a void* to bypass the Dart wrappers so
+// that we can precisely control the life cycle.
+void* createAutoreleasePool() {
+ return (void*)[NSAutoreleasePool new];
}
-void destroyAutoreleasePool(id pool) {
- [pool release];
+void destroyAutoreleasePool(void* pool) {
+ [((NSAutoreleasePool*)pool) release];
}
@implementation RefCounted
diff --git a/pkgs/ffigen/test/native_objc_test/block_config.yaml b/pkgs/ffigen/test/native_objc_test/block_config.yaml
index 841439e..9f12686 100644
--- a/pkgs/ffigen/test/native_objc_test/block_config.yaml
+++ b/pkgs/ffigen/test/native_objc_test/block_config.yaml
@@ -6,6 +6,9 @@
objc-interfaces:
include:
- BlockTester
+functions:
+ include:
+ - getBlockRetainCount
headers:
entry-points:
- 'block_test.m'
diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart
index 7ca61d6..d2068ce 100644
--- a/pkgs/ffigen/test/native_objc_test/block_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/block_test.dart
@@ -238,26 +238,26 @@
Pointer<Void> funcPointerBlockRefCountTest() {
final block =
IntBlock.fromFunctionPointer(lib, Pointer.fromFunction(_add100, 999));
- expect(BlockTester.getBlockRetainCount_(lib, block.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
return block.pointer.cast();
}
test('Function pointer block ref counting', () {
final rawBlock = funcPointerBlockRefCountTest();
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, rawBlock), 0);
+ expect(lib.getBlockRetainCount(rawBlock), 0);
});
Pointer<Void> funcBlockRefCountTest() {
final block = IntBlock.fromFunction(lib, makeAdder(4000));
- expect(BlockTester.getBlockRetainCount_(lib, block.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
return block.pointer.cast();
}
test('Function block ref counting', () {
final rawBlock = funcBlockRefCountTest();
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, rawBlock), 0);
+ expect(lib.getBlockRetainCount(rawBlock), 0);
});
(Pointer<Void>, Pointer<Void>, Pointer<Void>)
@@ -276,13 +276,10 @@
// One reference held by inputBlock object, another bound to the
// outputBlock lambda.
- expect(
- BlockTester.getBlockRetainCount_(lib, inputBlock.pointer.cast()), 2);
+ expect(lib.getBlockRetainCount(inputBlock.pointer.cast()), 2);
- expect(
- BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
- expect(
- BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (
inputBlock.pointer.cast(),
blockBlock.pointer.cast(),
@@ -297,10 +294,10 @@
// This leaks because block functions aren't cleaned up at the moment.
// TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak.
- expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 1);
+ expect(lib.getBlockRetainCount(inputBlock), 1);
- expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
- expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ expect(lib.getBlockRetainCount(blockBlock), 0);
+ expect(lib.getBlockRetainCount(outputBlock), 0);
});
(Pointer<Void>, Pointer<Void>, Pointer<Void>)
@@ -316,11 +313,9 @@
expect(outputBlock(1), 6);
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 2);
- expect(
- BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
- expect(
- BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(inputBlock), 2);
+ expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (
inputBlock,
blockBlock.pointer.cast(),
@@ -335,10 +330,10 @@
// This leaks because block functions aren't cleaned up at the moment.
// TODO(https://github.com/dart-lang/ffigen/issues/428): Fix this leak.
- expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 2);
+ expect(lib.getBlockRetainCount(inputBlock), 2);
- expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
- expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ expect(lib.getBlockRetainCount(blockBlock), 0);
+ expect(lib.getBlockRetainCount(outputBlock), 0);
});
(Pointer<Void>, Pointer<Void>, Pointer<Void>)
@@ -353,13 +348,10 @@
// One reference held by inputBlock object, another held internally by the
// ObjC implementation of the blockBlock.
- expect(
- BlockTester.getBlockRetainCount_(lib, inputBlock.pointer.cast()), 2);
+ expect(lib.getBlockRetainCount(inputBlock.pointer.cast()), 2);
- expect(
- BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
- expect(
- BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (
inputBlock.pointer.cast(),
blockBlock.pointer.cast(),
@@ -371,9 +363,9 @@
final (inputBlock, blockBlock, outputBlock) =
nativeBlockBlockDartCallRefCountTest();
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, inputBlock), 0);
- expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
- expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ expect(lib.getBlockRetainCount(inputBlock), 0);
+ expect(lib.getBlockRetainCount(blockBlock), 0);
+ expect(lib.getBlockRetainCount(outputBlock), 0);
});
(Pointer<Void>, Pointer<Void>) nativeBlockBlockObjCCallRefCountTest() {
@@ -382,18 +374,16 @@
expect(outputBlock(1), 14);
doGC();
- expect(
- BlockTester.getBlockRetainCount_(lib, blockBlock.pointer.cast()), 1);
- expect(
- BlockTester.getBlockRetainCount_(lib, outputBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
+ expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (blockBlock.pointer.cast(), outputBlock.pointer.cast());
}
test('Calling a native block block from ObjC has correct ref counting', () {
final (blockBlock, outputBlock) = nativeBlockBlockObjCCallRefCountTest();
doGC();
- expect(BlockTester.getBlockRetainCount_(lib, blockBlock), 0);
- expect(BlockTester.getBlockRetainCount_(lib, outputBlock), 0);
+ expect(lib.getBlockRetainCount(blockBlock), 0);
+ expect(lib.getBlockRetainCount(outputBlock), 0);
});
(Pointer<Int32>, Pointer<Int32>) objectBlockRefCountTest(Allocator alloc) {
diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m
index 3dd4289..d27a97c 100644
--- a/pkgs/ffigen/test/native_objc_test/block_test.m
+++ b/pkgs/ffigen/test/native_objc_test/block_test.m
@@ -5,6 +5,8 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSThread.h>
+#include "util.h"
+
typedef struct {
double x;
double y;
@@ -61,7 +63,6 @@
}
+ (BlockTester*)makeFromBlock:(IntBlock)block;
+ (BlockTester*)makeFromMultiplier:(int32_t)mult;
-+ (uint64_t)getBlockRetainCount:(void*)block;
- (int32_t)call:(int32_t)x;
- (IntBlock)getBlock;
- (void)pokeBlock;
@@ -91,33 +92,6 @@
return bt;
}
-typedef struct {
- void* isa;
- int flags;
- // There are other fields, but we just need the flags and isa.
-} BlockRefCountExtractor;
-
-void* valid_block_isa = NULL;
-+ (uint64_t)getBlockRetainCount:(void*)block {
- BlockRefCountExtractor* b = (BlockRefCountExtractor*)block;
- // HACK: The only way I can find to reliably figure out that a block has been
- // deleted is to check the isa field (the lower bits of the flags field seem
- // to be randomized, not just set to 0). But we also don't know the value this
- // field has when it's constructed (copying the block changes it from
- // _NSConcreteGlobalBlock to an internal value). So we assume that the first
- // time this function is called, we have a valid block, and on subsequent
- // calls we check to see if the isa field changed.
- if (valid_block_isa == NULL) {
- valid_block_isa = b->isa;
- }
- if (b->isa != valid_block_isa) {
- return 0;
- }
- // The ref count is stored in the lower bits of the flags field, but skips the
- // 0x1 bit.
- return (b->flags & 0xFFFF) >> 1;
-}
-
- (int32_t)call:(int32_t)x {
return myBlock(x);
}
diff --git a/pkgs/ffigen/test/native_objc_test/static_func_config.yaml b/pkgs/ffigen/test/native_objc_test/static_func_config.yaml
new file mode 100644
index 0000000..bbafeaa
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/static_func_config.yaml
@@ -0,0 +1,18 @@
+name: StaticFuncTestObjCLibrary
+description: 'Test ObjC static functions'
+language: objc
+output: 'static_func_bindings.dart'
+exclude-all-by-default: true
+functions:
+ include:
+ - getBlockRetainCount
+ - staticFuncOfObject
+ - staticFuncOfNullableObject
+ - staticFuncOfBlock
+ - staticFuncReturnsRetained
+ - staticFuncReturnsRetainedArg
+headers:
+ entry-points:
+ - 'static_func_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/static_func_native_config.yaml b/pkgs/ffigen/test/native_objc_test/static_func_native_config.yaml
new file mode 100644
index 0000000..2a591c9
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/static_func_native_config.yaml
@@ -0,0 +1,19 @@
+name: StaticFuncTestObjCLibrary
+description: 'Test ObjC static functions using @Native'
+language: objc
+output: 'static_func_native_bindings.dart'
+exclude-all-by-default: true
+ffi-native:
+functions:
+ include:
+ - getBlockRetainCount
+ - staticFuncOfObject
+ - staticFuncOfNullableObject
+ - staticFuncOfBlock
+ - staticFuncReturnsRetained
+ - staticFuncReturnsRetainedArg
+headers:
+ entry-points:
+ - 'static_func_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/static_func_native_test.dart b/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart
new file mode 100644
index 0000000..93cbcad
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/static_func_native_test.dart
@@ -0,0 +1,156 @@
+// Copyright (c) 2023, 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')
+
+// Keep in sync with static_func_test.dart. These are the same tests, but using
+// @Native.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:ffi/ffi.dart';
+import '../test_utils.dart';
+import 'static_func_native_bindings.dart';
+import 'util.dart';
+
+typedef IntBlock = ObjCBlock_Int32_Int32;
+
+void main() {
+ late StaticFuncTestObjCLibrary lib;
+ late void Function(Pointer<Char>, Pointer<Void>) executeInternalCommand;
+
+ group('static functions', () {
+ setUpAll(() {
+ logWarnings();
+ final dylib = File('test/native_objc_test/static_func_test.dylib');
+ verifySetupFile(dylib);
+ lib = StaticFuncTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
+
+ executeInternalCommand = DynamicLibrary.process().lookupFunction<
+ Void Function(Pointer<Char>, Pointer<Void>),
+ void Function(
+ Pointer<Char>, Pointer<Void>)>('Dart_ExecuteInternalCommand');
+
+ generateBindingsForCoverage('static_func');
+ });
+
+ doGC() {
+ final gcNow = "gc-now".toNativeUtf8();
+ executeInternalCommand(gcNow.cast(), nullptr);
+ calloc.free(gcNow);
+ }
+
+ Pointer<Int32> staticFuncOfObjectRefCountTest(Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final obj = StaticFuncTestObj.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+
+ final outputObj = staticFuncOfObject(lib, obj);
+ expect(obj, outputObj);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test('Objects passed through static functions have correct ref counts', () {
+ using((Arena arena) {
+ final (counter) = staticFuncOfObjectRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+ });
+ });
+
+ Pointer<Int32> staticFuncOfNullableObjectRefCountTest(Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final obj = StaticFuncTestObj.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+
+ final outputObj = staticFuncOfNullableObject(lib, obj);
+ expect(obj, outputObj);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test('Nullables passed through static functions have correct ref counts',
+ () {
+ using((Arena arena) {
+ final (counter) = staticFuncOfNullableObjectRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+
+ expect(staticFuncOfNullableObject(lib, null), isNull);
+ });
+ });
+
+ Pointer<Void> staticFuncOfBlockRefCountTest() {
+ final block = IntBlock.fromFunction(lib, (int x) => 2 * x);
+ expect(getBlockRetainCount(block.pointer.cast()), 1);
+
+ final outputBlock = staticFuncOfBlock(lib, block);
+ expect(block, outputBlock);
+ expect(getBlockRetainCount(block.pointer.cast()), 2);
+
+ return block.pointer.cast();
+ }
+
+ test('Blocks passed through static functions have correct ref counts', () {
+ final (rawBlock) = staticFuncOfBlockRefCountTest();
+ doGC();
+ expect(getBlockRetainCount(rawBlock), 0);
+ });
+
+ Pointer<Int32> staticFuncReturnsRetainedRefCountTest(Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final outputObj = staticFuncReturnsRetained(lib, counter);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test(
+ 'Objects returned from static functions with NS_RETURNS_RETAINED '
+ 'have correct ref counts', () {
+ using((Arena arena) {
+ final (counter) = staticFuncReturnsRetainedRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+ });
+ });
+
+ Pointer<Int32> staticFuncOfObjectReturnsRetainedRefCountTest(
+ Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final obj = StaticFuncTestObj.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+
+ final outputObj = staticFuncReturnsRetainedArg(lib, obj);
+ expect(obj, outputObj);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test(
+ 'Objects passed through static functions with NS_RETURNS_RETAINED '
+ 'have correct ref counts', () {
+ using((Arena arena) {
+ final (counter) = staticFuncOfObjectReturnsRetainedRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+ });
+ });
+ });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/static_func_test.dart b/pkgs/ffigen/test/native_objc_test/static_func_test.dart
new file mode 100644
index 0000000..f3d9c01
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/static_func_test.dart
@@ -0,0 +1,156 @@
+// Copyright (c) 2023, 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')
+
+// Keep in sync with static_func_test.dart. These are the same tests, but
+// without using @Native.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:ffi/ffi.dart';
+import '../test_utils.dart';
+import 'static_func_bindings.dart';
+import 'util.dart';
+
+typedef IntBlock = ObjCBlock_Int32_Int32;
+
+void main() {
+ late StaticFuncTestObjCLibrary lib;
+ late void Function(Pointer<Char>, Pointer<Void>) executeInternalCommand;
+
+ group('static functions', () {
+ setUpAll(() {
+ logWarnings();
+ final dylib = File('test/native_objc_test/static_func_test.dylib');
+ verifySetupFile(dylib);
+ lib = StaticFuncTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
+
+ executeInternalCommand = DynamicLibrary.process().lookupFunction<
+ Void Function(Pointer<Char>, Pointer<Void>),
+ void Function(
+ Pointer<Char>, Pointer<Void>)>('Dart_ExecuteInternalCommand');
+
+ generateBindingsForCoverage('static_func');
+ });
+
+ doGC() {
+ final gcNow = "gc-now".toNativeUtf8();
+ executeInternalCommand(gcNow.cast(), nullptr);
+ calloc.free(gcNow);
+ }
+
+ Pointer<Int32> staticFuncOfObjectRefCountTest(Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final obj = StaticFuncTestObj.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+
+ final outputObj = lib.staticFuncOfObject(obj);
+ expect(obj, outputObj);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test('Objects passed through static functions have correct ref counts', () {
+ using((Arena arena) {
+ final (counter) = staticFuncOfObjectRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+ });
+ });
+
+ Pointer<Int32> staticFuncOfNullableObjectRefCountTest(Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final obj = StaticFuncTestObj.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+
+ final outputObj = lib.staticFuncOfNullableObject(obj);
+ expect(obj, outputObj);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test('Nullables passed through static functions have correct ref counts',
+ () {
+ using((Arena arena) {
+ final (counter) = staticFuncOfNullableObjectRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+
+ expect(lib.staticFuncOfNullableObject(null), isNull);
+ });
+ });
+
+ Pointer<Void> staticFuncOfBlockRefCountTest() {
+ final block = IntBlock.fromFunction(lib, (int x) => 2 * x);
+ expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
+
+ final outputBlock = lib.staticFuncOfBlock(block);
+ expect(block, outputBlock);
+ expect(lib.getBlockRetainCount(block.pointer.cast()), 2);
+
+ return block.pointer.cast();
+ }
+
+ test('Blocks passed through static functions have correct ref counts', () {
+ final (rawBlock) = staticFuncOfBlockRefCountTest();
+ doGC();
+ expect(lib.getBlockRetainCount(rawBlock), 0);
+ });
+
+ Pointer<Int32> staticFuncReturnsRetainedRefCountTest(Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final outputObj = lib.staticFuncReturnsRetained(counter);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test(
+ 'Objects returned from static functions with NS_RETURNS_RETAINED '
+ 'have correct ref counts', () {
+ using((Arena arena) {
+ final (counter) = staticFuncReturnsRetainedRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+ });
+ });
+
+ Pointer<Int32> staticFuncOfObjectReturnsRetainedRefCountTest(
+ Allocator alloc) {
+ final counter = alloc<Int32>();
+ counter.value = 0;
+
+ final obj = StaticFuncTestObj.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+
+ final outputObj = lib.staticFuncReturnsRetainedArg(obj);
+ expect(obj, outputObj);
+ expect(counter.value, 1);
+
+ return counter;
+ }
+
+ test(
+ 'Objects passed through static functions with NS_RETURNS_RETAINED '
+ 'have correct ref counts', () {
+ using((Arena arena) {
+ final (counter) = staticFuncOfObjectReturnsRetainedRefCountTest(arena);
+ doGC();
+ expect(counter.value, 0);
+ });
+ });
+ });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/static_func_test.m b/pkgs/ffigen/test/native_objc_test/static_func_test.m
new file mode 100644
index 0000000..5fc3a14
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/static_func_test.m
@@ -0,0 +1,63 @@
+// Copyright (c) 2023, 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/NSString.h>
+
+#include "util.h"
+
+@interface StaticFuncTestObj : NSObject {
+ int32_t* counter;
+}
++ (instancetype)newWithCounter:(int32_t*) _counter;
+- (instancetype)initWithCounter:(int32_t*) _counter;
+- (void)setCounter:(int32_t*) _counter;
+- (void)dealloc;
+@end
+
+StaticFuncTestObj* staticFuncOfObject(StaticFuncTestObj* a) {
+ return a;
+}
+
+StaticFuncTestObj* _Nullable staticFuncOfNullableObject(
+ StaticFuncTestObj* _Nullable a) {
+ return a;
+}
+
+typedef int32_t (^IntBlock)(int32_t);
+IntBlock staticFuncOfBlock(IntBlock a) {
+ return a;
+}
+
+NS_RETURNS_RETAINED StaticFuncTestObj* staticFuncReturnsRetained(
+ int32_t* counter) {
+ return [StaticFuncTestObj newWithCounter: counter];
+}
+
+NS_RETURNS_RETAINED StaticFuncTestObj* staticFuncReturnsRetainedArg(
+ StaticFuncTestObj* a) {
+ return [a retain];
+}
+
+
+@implementation StaticFuncTestObj
++ (instancetype)newWithCounter:(int32_t*) _counter {
+ return [[StaticFuncTestObj alloc] initWithCounter: _counter];
+}
+
+- (instancetype)initWithCounter:(int32_t*) _counter {
+ counter = _counter;
+ ++*counter;
+ return [super init];
+}
+
+- (void)setCounter:(int32_t*) _counter {
+ counter = _counter;
+ ++*counter;
+}
+
+- (void)dealloc {
+ if (counter != nil) --*counter;
+ [super dealloc];
+}
+@end
diff --git a/pkgs/ffigen/test/native_objc_test/util.h b/pkgs/ffigen/test/native_objc_test/util.h
new file mode 100644
index 0000000..0694aea
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/util.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, 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.
+
+#ifndef _TEST_UTIL_H_
+#define _TEST_UTIL_H_
+
+typedef struct {
+ void* isa;
+ int flags;
+ // There are other fields, but we just need the flags and isa.
+} BlockRefCountExtractor;
+
+static void* valid_block_isa = NULL;
+uint64_t getBlockRetainCount(void* block) {
+ BlockRefCountExtractor* b = (BlockRefCountExtractor*)block;
+ // HACK: The only way I can find to reliably figure out that a block has been
+ // deleted is to check the isa field (the lower bits of the flags field seem
+ // to be randomized, not just set to 0). But we also don't know the value this
+ // field has when it's constructed (copying the block changes it from
+ // _NSConcreteGlobalBlock to an internal value). So we assume that the first
+ // time this function is called, we have a valid block, and on subsequent
+ // calls we check to see if the isa field changed.
+ if (valid_block_isa == NULL) {
+ valid_block_isa = b->isa;
+ }
+ if (b->isa != valid_block_isa) {
+ return 0;
+ }
+ // The ref count is stored in the lower bits of the flags field, but skips the
+ // 0x1 bit.
+ return (b->flags & 0xFFFF) >> 1;
+}
+
+#endif // _TEST_UTIL_H_