Skip to content

[ffigen] Method families #1463

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
if you had a line like `s.requires_arc = []` in your podspec, this should
either be removed, or you should add the ffigen generated ObjC code to the
list. If you're compiling directly with clang, add the `-fobjc-arc` flag.
- Fix some bugs in the way ObjC method families and ownership annotations were
being handled: https://github.com/dart-lang/native/issues/1446

## 13.0.0

Expand Down
7 changes: 5 additions & 2 deletions pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,10 @@ class ObjCInterface extends BindingType with ObjCMethods {
}
s.write(m.msgSend!.invoke(
w,
isStatic ? _classObject.name : 'this.pointer',
isStatic
? _classObject.name
: convertDartTypeToFfiDartType(w, 'this',
objCRetain: m.consumesSelf),
m.selObject!.name,
m.params.map((p) => p.type
.convertDartTypeToFfiDartType(w, p.name, objCRetain: false)),
Expand All @@ -198,7 +201,7 @@ class ObjCInterface extends BindingType with ObjCMethods {
final result = returnType.convertFfiDartTypeToDartType(
w,
'_ret',
objCRetain: !m.isOwnedReturn,
objCRetain: !m.returnsRetained,
objCEnclosingClass: name,
);
s.write(' return $result;');
Expand Down
67 changes: 59 additions & 8 deletions pkgs/ffigen/lib/src/code_generator/objc_methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,51 @@ enum ObjCMethodKind {
propertySetter,
}

enum ObjCMethodOwnership {
retained,
notRetained,
autoreleased,
}

// In ObjC, the name of a method affects its ref counting semantics. See
// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#method-families
enum ObjCMethodFamily {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a link to the docs here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

alloc('alloc', returnsRetained: true, consumesSelf: false),
init('init', returnsRetained: true, consumesSelf: true),
new_('new', returnsRetained: true, consumesSelf: false),
copy('copy', returnsRetained: true, consumesSelf: false),
mutableCopy('mutableCopy', returnsRetained: true, consumesSelf: false);

final String name;
final bool returnsRetained;
final bool consumesSelf;

const ObjCMethodFamily(this.name,
{required this.returnsRetained, required this.consumesSelf});

static ObjCMethodFamily? parse(String methodName) {
final name = methodName.substring(_findFamilyStart(methodName));
for (final family in ObjCMethodFamily.values) {
if (_matchesFamily(name, family.name)) return family;
}
return null;
}

static int _findFamilyStart(String methodName) {
for (var i = 0; i < methodName.length; ++i) {
if (methodName[i] != '_') return i;
}
return methodName.length;
}

static final _lowerCase = RegExp('[a-z]');
static bool _matchesFamily(String name, String familyName) {
if (!name.startsWith(familyName)) return false;
final tail = name.substring(familyName.length);
return !tail.startsWith(_lowerCase);
}
}

class ObjCProperty {
final String originalName;
String? dartName;
Expand All @@ -123,7 +168,9 @@ class ObjCMethod {
final ObjCMethodKind kind;
final bool isClassMethod;
final bool isOptional;
bool returnsRetained = false;
ObjCMethodOwnership? ownershipAttribute;
final ObjCMethodFamily? family;
bool consumesSelfAttribute = false;
ObjCInternalGlobal? selObject;
ObjCMsgSendFunc? msgSend;
late ObjCBlock protocolBlock;
Expand All @@ -136,6 +183,7 @@ class ObjCMethod {
required this.isClassMethod,
required this.isOptional,
required this.returnType,
required this.family,
List<ObjCMethodParam>? params_,
}) : params = params_ ?? [];

Expand Down Expand Up @@ -201,12 +249,15 @@ class ObjCMethod {
return msgSend == other.msgSend;
}

static final _copyRegExp = RegExp('[cC]opy');
bool get isOwnedReturn =>
returnsRetained ||
originalName.startsWith('new') ||
originalName.startsWith('alloc') ||
originalName.contains(_copyRegExp);
bool get returnsRetained {
if (ownershipAttribute != null) {
return ownershipAttribute == ObjCMethodOwnership.retained;
}
return family?.returnsRetained ?? false;
}

bool get consumesSelf =>
consumesSelfAttribute || (family?.consumesSelf ?? false);

Iterable<Type> get childTypes sync* {
yield returnType;
Expand All @@ -216,7 +267,7 @@ class ObjCMethod {
}

@override
String toString() => '${isOptional ? "@optional " : ""}$returnType '
String toString() => '${isOptional ? '@optional ' : ''}$returnType '
'$originalName(${params.join(', ')})';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: fieldType,
family: null,
);
itf.addMethod(getter);

Expand All @@ -160,13 +161,15 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
.clang_Cursor_getObjCPropertySetterName(cursor)
.toStringAndDispose();
final setter = ObjCMethod(
originalName: setterName,
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertySetter,
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: NativeType(SupportedNativeType.voidType));
originalName: setterName,
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertySetter,
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: NativeType(SupportedNativeType.voidType),
family: null,
);
setter.params.add(ObjCMethodParam(fieldType, 'value'));
itf.addMethod(setter);
}
Expand Down Expand Up @@ -204,6 +207,7 @@ ObjCMethod? parseObjCMethod(clang_types.CXCursor cursor, String itfName) {
isClassMethod: isClassMethod,
isOptional: isOptionalMethod,
returnType: returnType,
family: ObjCMethodFamily.parse(methodName),
);
_logger.fine(' > ${isClassMethod ? 'Class' : 'Instance'} method: '
'${method.originalName} ${cursor.completeStringRepr()}');
Expand All @@ -216,7 +220,16 @@ ObjCMethod? parseObjCMethod(clang_types.CXCursor cursor, String itfName) {
}
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsRetained:
method.returnsRetained = true;
method.ownershipAttribute = ObjCMethodOwnership.retained;
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsNotRetained:
method.ownershipAttribute = ObjCMethodOwnership.notRetained;
break;
case clang_types.CXCursorKind.CXCursor_NSReturnsAutoreleased:
method.ownershipAttribute = ObjCMethodOwnership.autoreleased;
break;
case clang_types.CXCursorKind.CXCursor_NSConsumesSelf:
method.consumesSelfAttribute = true;
break;
default:
}
Expand Down
63 changes: 59 additions & 4 deletions pkgs/ffigen/test/native_objc_test/arc_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,49 +113,104 @@ void main() {
});

(
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>,
Pointer<ObjCObject>
) copyMethodsInner(Pointer<Int32> counter) {
final pool = lib.objc_autoreleasePoolPush();
final obj1 = ArcTestObject.newWithCounter_(counter);
expect(counter.value, 1);
final obj2 = obj1.copyMe();
expect(counter.value, 2);
final obj3 = obj1.makeACopy();
final obj3 = obj1.mutableCopyMe();
expect(counter.value, 3);
final obj4 = obj1.copyWithZone_(nullptr);
expect(counter.value, 4);
final obj5 = obj1.copy();
expect(counter.value, 5);
final obj6 = obj1.returnsRetained();
expect(counter.value, 6);
final obj7 = obj1.copyMeNoRetain();
expect(counter.value, 7);
final obj8 = obj1.copyMeAutorelease();
expect(counter.value, 8);
final obj9 = obj1.copyMeConsumeSelf();
expect(counter.value, 9);

final obj1raw = obj1.pointer;
final obj2raw = obj2.pointer;
final obj3raw = obj3.pointer;
final obj4raw = obj4.pointer;
final obj5raw = obj5.pointer;
final obj6raw = obj6.pointer;
final obj7raw = obj7.pointer;
final obj8raw = obj8.pointer;
final obj9raw = obj9.pointer;

expect(objectRetainCount(obj1raw), 1);
expect(objectRetainCount(obj2raw), 1);
expect(objectRetainCount(obj3raw), 1);
expect(objectRetainCount(obj4raw), 1);
expect(objectRetainCount(obj5raw), 1);
expect(objectRetainCount(obj6raw), 1);
expect(objectRetainCount(obj7raw), 2); // One ref in autorelease pool.
expect(objectRetainCount(obj8raw), 2); // One ref in autorelease pool.
expect(objectRetainCount(obj9raw), 1);

return (obj1raw, obj2raw, obj3raw, obj4raw, obj5raw);
lib.objc_autoreleasePoolPop(pool);
expect(objectRetainCount(obj1raw), 1);
expect(objectRetainCount(obj2raw), 1);
expect(objectRetainCount(obj3raw), 1);
expect(objectRetainCount(obj4raw), 1);
expect(objectRetainCount(obj5raw), 1);
expect(objectRetainCount(obj6raw), 1);
expect(objectRetainCount(obj7raw), 1);
expect(objectRetainCount(obj8raw), 1);
expect(objectRetainCount(obj9raw), 1);

return (
obj1raw,
obj2raw,
obj3raw,
obj4raw,
obj5raw,
obj6raw,
obj7raw,
obj8raw,
obj9raw
);
}

test('copy methods ref count correctly', () {
final counter = calloc<Int32>();
counter.value = 0;
final (obj1raw, obj2raw, obj3raw, obj4raw, obj5raw) =
copyMethodsInner(counter);
final (
obj1raw,
obj2raw,
obj3raw,
obj4raw,
obj5raw,
obj6raw,
obj7raw,
obj8raw,
obj9raw
) = copyMethodsInner(counter);
doGC();
expect(objectRetainCount(obj1raw), 0);
expect(objectRetainCount(obj2raw), 0);
expect(objectRetainCount(obj3raw), 0);
expect(objectRetainCount(obj4raw), 0);
expect(objectRetainCount(obj5raw), 0);
expect(objectRetainCount(obj6raw), 0);
expect(objectRetainCount(obj7raw), 0);
expect(objectRetainCount(obj8raw), 0);
expect(objectRetainCount(obj9raw), 0);
expect(counter.value, 0);
calloc.free(counter);
});
Expand Down
21 changes: 18 additions & 3 deletions pkgs/ffigen/test/native_objc_test/arc_test.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ + (ArcTestObject*)makeAndAutorelease:(int32_t*) _counter;
- (void)setCounter:(int32_t*) _counter;
- (void)dealloc;
- (ArcTestObject*)copyMe;
- (ArcTestObject*)makeACopy NS_RETURNS_RETAINED;
- (ArcTestObject*)mutableCopyMe;
- (id)copyWithZone:(NSZone*) zone;
- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED;
- (ArcTestObject*)copyMeNoRetain __attribute__((ns_returns_not_retained));
- (ArcTestObject*)copyMeAutorelease __attribute__((ns_returns_autoreleased));
- (ArcTestObject*)copyMeConsumeSelf __attribute__((ns_consumes_self));

@property (assign) ArcTestObject* assignedProperty;
@property (retain) ArcTestObject* retainedProperty;
Expand Down Expand Up @@ -67,7 +70,7 @@ - (ArcTestObject*)copyMe {
return [[ArcTestObject alloc] initWithCounter: counter];
}

- (ArcTestObject*)makeACopy NS_RETURNS_RETAINED {
- (ArcTestObject*)mutableCopyMe {
return [[ArcTestObject alloc] initWithCounter: counter];
}

Expand All @@ -76,7 +79,19 @@ - (id)copyWithZone:(NSZone*) zone {
}

- (ArcTestObject*)returnsRetained NS_RETURNS_RETAINED {
return self;
return [self copyMe];
}

- (ArcTestObject*)copyMeNoRetain __attribute__((ns_returns_not_retained)) {
return [self copyMe];
}

- (ArcTestObject*)copyMeAutorelease __attribute__((ns_returns_autoreleased)) {
return [self copyMe];
}

- (ArcTestObject*)copyMeConsumeSelf __attribute__((ns_consumes_self)) {
return [self copyMe];
}

@end
4 changes: 2 additions & 2 deletions pkgs/ffigen/test/native_objc_test/ref_count_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ output: 'ref_count_bindings.dart'
exclude-all-by-default: true
functions:
include:
- createAutoreleasePool
- destroyAutoreleasePool
- objc_autoreleasePoolPop
- objc_autoreleasePoolPush
objc-interfaces:
include:
- RefCountTestObject
Expand Down
Loading