[ffigen] Handle NS_RETURNS_RETAINED and methods that start with new/alloc or contain copy (#380)
* Handle NS_RETURNS_RETAINED and methods that start with init/alloc
* Split up tests
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index d8216e9..95143f2 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -387,6 +387,7 @@
final List<ObjCMethodParam> params;
final ObjCMethodKind kind;
final bool isClass;
+ bool returnsRetained = false;
ObjCInternalGlobal? selObject;
Func? msgSend;
@@ -445,7 +446,12 @@
return msgSend == other.msgSend;
}
- bool get isOwnedReturn => originalName == 'new' || originalName == 'alloc';
+ static final _copyRegExp = RegExp('[cC]opy');
+ bool get isOwnedReturn =>
+ returnsRetained ||
+ originalName.startsWith('new') ||
+ originalName.startsWith('alloc') ||
+ originalName.contains(_copyRegExp);
}
class ObjCMethodParam {
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 be660df..bf9d038 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
@@ -222,6 +222,9 @@
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;
@@ -267,6 +270,10 @@
.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
diff --git a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart b/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart
index 59261fe..2c3ab47 100644
--- a/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart
@@ -41,20 +41,57 @@
calloc.free(gcNow);
}
- verifyRefCountsInner(Pointer<Int32> counter) {
- final obj1 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ newMethodsInner(Pointer<Int32> counter) {
+ final obj1 = ArcTestObject.new1(lib);
+ obj1.setCounter_(counter);
expect(counter.value, 1);
- final obj2 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ final obj2 = ArcTestObject.newWithCounter_(lib, counter);
expect(counter.value, 2);
- final obj3 = ArcTestObject.alloc(lib).initWithCounter_(counter);
- expect(counter.value, 3);
}
- test('Verify ref counts', () {
+ test('new methods ref count correctly', () {
// To get the GC to work correctly, the references to the objects all have
// to be in a separate function.
final counter = calloc<Int32>();
- verifyRefCountsInner(counter);
+ counter.value = 0;
+ newMethodsInner(counter);
+ doGC();
+ expect(counter.value, 0);
+ calloc.free(counter);
+ });
+
+ allocMethodsInner(Pointer<Int32> counter) {
+ final obj1 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ expect(counter.value, 1);
+ final obj2 = ArcTestObject.castFrom(ArcTestObject.alloc(lib).init());
+ obj2.setCounter_(counter);
+ expect(counter.value, 2);
+ final obj3 = ArcTestObject.allocTheThing(lib).initWithCounter_(counter);
+ expect(counter.value, 3);
+ }
+
+ test('alloc and init methods ref count correctly', () {
+ final counter = calloc<Int32>();
+ counter.value = 0;
+ allocMethodsInner(counter);
+ doGC();
+ expect(counter.value, 0);
+ calloc.free(counter);
+ });
+
+ copyMethodsInner(Pointer<Int32> counter) {
+ final obj1 = ArcTestObject.newWithCounter_(lib, counter);
+ expect(counter.value, 1);
+ final obj2 = obj1.copyMe();
+ expect(counter.value, 2);
+ final obj3 = obj1.makeACopy();
+ expect(counter.value, 3);
+ }
+
+ test('copy methods ref count correctly', () {
+ final counter = calloc<Int32>();
+ counter.value = 0;
+ copyMethodsInner(counter);
doGC();
expect(counter.value, 0);
calloc.free(counter);
@@ -62,15 +99,11 @@
test('Manual release', () {
final counter = calloc<Int32>();
- final obj1 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ final obj1 = ArcTestObject.newWithCounter_(lib, counter);
expect(counter.value, 1);
- final obj2 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ final obj2 = ArcTestObject.newWithCounter_(lib, counter);
expect(counter.value, 2);
- final obj3 = ArcTestObject.alloc(lib).initWithCounter_(counter);
- expect(counter.value, 3);
-
- // GC to clean up temporaries created between alloc and initWithCounter_.
- doGC();
+ final obj3 = ArcTestObject.newWithCounter_(lib, counter);
expect(counter.value, 3);
obj1.release();
@@ -85,7 +118,8 @@
});
ArcTestObject unownedReferenceInner2(Pointer<Int32> counter) {
- final obj1 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ final obj1 = ArcTestObject.new1(lib);
+ obj1.setCounter_(counter);
expect(counter.value, 1);
final obj1b = obj1.unownedReference();
expect(counter.value, 1);
@@ -93,7 +127,8 @@
// Make a second object so that the counter check in unownedReferenceInner
// sees some sort of change. Otherwise this test could pass just by the GC
// not working correctly.
- final obj2 = ArcTestObject.alloc(lib).initWithCounter_(counter);
+ final obj2 = ArcTestObject.new1(lib);
+ obj2.setCounter_(counter);
expect(counter.value, 2);
return obj1b;
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 6c3186d..4fa6c10 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
@@ -8,20 +8,39 @@
int32_t* counter;
}
++ (instancetype)allocTheThing;
++ (instancetype)newWithCounter:(int32_t*) _counter;
- (instancetype)initWithCounter:(int32_t*) _counter;
+- (void)setCounter:(int32_t*) _counter;
- (void)dealloc;
- (ArcTestObject*)unownedReference;
+- (ArcTestObject*)copyMe;
+- (ArcTestObject*)makeACopy;
+- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED;
@end
@implementation ArcTestObject
++ (instancetype)allocTheThing {
+ return [ArcTestObject alloc];
+}
+
++ (instancetype)newWithCounter:(int32_t*) _counter {
+ return [[ArcTestObject alloc] initWithCounter: _counter];
+}
+
- (instancetype)initWithCounter:(int32_t*) _counter {
counter = _counter;
++*counter;
return [super init];
}
+- (void)setCounter:(int32_t*) _counter {
+ counter = _counter;
+ ++*counter;
+}
+
- (void)dealloc {
--*counter;
[super dealloc];
@@ -31,4 +50,16 @@
return self;
}
+- (ArcTestObject*)copyMe {
+ return [[ArcTestObject alloc] initWithCounter: counter];
+}
+
+- (ArcTestObject*)makeACopy {
+ return [[ArcTestObject alloc] initWithCounter: counter];
+}
+
+- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED {
+ return [self retain];
+}
+
@end