Skip to content

Commit

Permalink
Make ObjC memory management errors more debuggable (#1111)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamappelbe committed May 5, 2024
1 parent 95d226d commit b2b0e32
Show file tree
Hide file tree
Showing 19 changed files with 539 additions and 233 deletions.
219 changes: 189 additions & 30 deletions pkgs/ffigen/test/native_objc_test/automated_ref_count_test.dart

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pkgs/ffigen/test/native_objc_test/automated_ref_count_test.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSAutoreleasePool.h>

#include "util.h"

@interface ArcTestObject : NSObject {
int32_t* counter;
}
Expand Down
3 changes: 0 additions & 3 deletions pkgs/ffigen/test/native_objc_test/block_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ exclude-all-by-default: true
objc-interfaces:
include:
- BlockTester
functions:
include:
- getBlockRetainCount
headers:
entry-points:
- 'block_test.m'
Expand Down
120 changes: 54 additions & 66 deletions pkgs/ffigen/test/native_objc_test/block_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:objective_c/objective_c.dart';
import 'package:objective_c/src/internal.dart' as internal_for_testing
show blockHasRegisteredClosure;
import 'package:test/test.dart';
Expand All @@ -31,16 +32,14 @@ typedef NullableObjectBlock = ObjCBlock_DummyObject_DummyObject1;
typedef BlockBlock = ObjCBlock_Int32Int32_Int32Int32;

void main() {
late BlockTestObjCLibrary lib;

group('Blocks', () {
setUpAll(() {
logWarnings();
// TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
DynamicLibrary.open('../objective_c/test/objective_c.dylib');
final dylib = File('test/native_objc_test/block_test.dylib');
verifySetupFile(dylib);
lib = BlockTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path));
DynamicLibrary.open(dylib.absolute.path);

generateBindingsForCoverage('block');
});
Expand Down Expand Up @@ -230,67 +229,67 @@ void main() {
expect(result2(1), 14);
});

Pointer<Void> funcPointerBlockRefCountTest() {
Pointer<ObjCBlock> funcPointerBlockRefCountTest() {
final block =
IntBlock.fromFunctionPointer(Pointer.fromFunction(_add100, 999));
expect(
internal_for_testing.blockHasRegisteredClosure(block.pointer), false);
expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
return block.pointer.cast();
expect(blockRetainCount(block.pointer), 1);
return block.pointer;
}

test('Function pointer block ref counting', () {
final rawBlock = funcPointerBlockRefCountTest();
doGC();
expect(lib.getBlockRetainCount(rawBlock), 0);
expect(blockRetainCount(rawBlock), 0);
});

Pointer<Void> funcBlockRefCountTest() {
Pointer<ObjCBlock> funcBlockRefCountTest() {
final block = IntBlock.fromFunction(makeAdder(4000));
expect(
internal_for_testing.blockHasRegisteredClosure(block.pointer), true);
expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
return block.pointer.cast();
expect(blockRetainCount(block.pointer), 1);
return block.pointer;
}

test('Function block ref counting', () async {
final rawBlock = funcBlockRefCountTest();
doGC();
await Future<void>.delayed(Duration.zero); // Let dispose message arrive.
expect(lib.getBlockRetainCount(rawBlock), 0);
expect(blockRetainCount(rawBlock), 0);
expect(internal_for_testing.blockHasRegisteredClosure(rawBlock.cast()),
false);
});

Pointer<Void> blockManualRetainRefCountTest() {
Pointer<ObjCBlock> blockManualRetainRefCountTest() {
final block = IntBlock.fromFunction(makeAdder(4000));
expect(
internal_for_testing.blockHasRegisteredClosure(block.pointer), true);
expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
final rawBlock = block.retainAndReturnPointer().cast<Void>();
expect(lib.getBlockRetainCount(rawBlock.cast()), 2);
return rawBlock.cast();
expect(blockRetainCount(block.pointer), 1);
final rawBlock = block.retainAndReturnPointer();
expect(blockRetainCount(rawBlock), 2);
return rawBlock;
}

int blockManualRetainRefCountTest2(Pointer<Void> rawBlock) {
int blockManualRetainRefCountTest2(Pointer<ObjCBlock> rawBlock) {
final block = IntBlock.castFromPointer(rawBlock.cast(),
retain: false, release: true);
return lib.getBlockRetainCount(block.pointer.cast());
return blockRetainCount(block.pointer);
}

test('Block ref counting with manual retain and release', () async {
final rawBlock = blockManualRetainRefCountTest();
doGC();
expect(lib.getBlockRetainCount(rawBlock), 1);
expect(blockRetainCount(rawBlock), 1);
expect(blockManualRetainRefCountTest2(rawBlock), 1);
doGC();
await Future<void>.delayed(Duration.zero); // Let dispose message arrive.
expect(lib.getBlockRetainCount(rawBlock), 0);
expect(blockRetainCount(rawBlock), 0);
expect(internal_for_testing.blockHasRegisteredClosure(rawBlock.cast()),
false);
});

(Pointer<Void>, Pointer<Void>, Pointer<Void>)
(Pointer<ObjCBlock>, Pointer<ObjCBlock>, Pointer<ObjCBlock>)
blockBlockDartCallRefCountTest() {
final inputBlock = IntBlock.fromFunction((int x) {
return 5 * x;
Expand All @@ -306,15 +305,11 @@ void main() {

// One reference held by inputBlock object, another bound to the
// outputBlock lambda.
expect(lib.getBlockRetainCount(inputBlock.pointer.cast()), 2);

expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (
inputBlock.pointer.cast(),
blockBlock.pointer.cast(),
outputBlock.pointer.cast()
);
expect(blockRetainCount(inputBlock.pointer), 2);

expect(blockRetainCount(blockBlock.pointer), 1);
expect(blockRetainCount(outputBlock.pointer), 1);
return (inputBlock.pointer, blockBlock.pointer, outputBlock.pointer);
}

test('Calling a block block from Dart has correct ref counting', () {
Expand All @@ -324,17 +319,17 @@ void main() {

// 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(lib.getBlockRetainCount(inputBlock), 1);
expect(blockRetainCount(inputBlock), 1);

expect(lib.getBlockRetainCount(blockBlock), 0);
expect(lib.getBlockRetainCount(outputBlock), 0);
expect(blockRetainCount(blockBlock), 0);
expect(blockRetainCount(outputBlock), 0);
});

(Pointer<Void>, Pointer<Void>, Pointer<Void>)
(Pointer<ObjCBlock>, Pointer<ObjCBlock>, Pointer<ObjCBlock>)
blockBlockObjCCallRefCountTest() {
late Pointer<Void> inputBlock;
late Pointer<ObjCBlock> inputBlock;
final blockBlock = BlockBlock.fromFunction((IntBlock intBlock) {
inputBlock = intBlock.pointer.cast();
inputBlock = intBlock.pointer;
return IntBlock.fromFunction((int x) {
return 3 * intBlock(x);
});
Expand All @@ -343,14 +338,10 @@ void main() {
expect(outputBlock(1), 6);
doGC();

expect(lib.getBlockRetainCount(inputBlock), 2);
expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (
inputBlock,
blockBlock.pointer.cast(),
outputBlock.pointer.cast()
);
expect(blockRetainCount(inputBlock), 2);
expect(blockRetainCount(blockBlock.pointer), 1);
expect(blockRetainCount(outputBlock.pointer), 1);
return (inputBlock, blockBlock.pointer, outputBlock.pointer);
}

test('Calling a block block from ObjC has correct ref counting', () {
Expand All @@ -360,13 +351,13 @@ void main() {

// 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(lib.getBlockRetainCount(inputBlock), 2);
expect(blockRetainCount(inputBlock), 2);

expect(lib.getBlockRetainCount(blockBlock), 0);
expect(lib.getBlockRetainCount(outputBlock), 0);
expect(blockRetainCount(blockBlock), 0);
expect(blockRetainCount(outputBlock), 0);
});

(Pointer<Void>, Pointer<Void>, Pointer<Void>)
(Pointer<ObjCBlock>, Pointer<ObjCBlock>, Pointer<ObjCBlock>)
nativeBlockBlockDartCallRefCountTest() {
final inputBlock = IntBlock.fromFunction((int x) {
return 5 * x;
Expand All @@ -378,42 +369,39 @@ void main() {

// One reference held by inputBlock object, another held internally by the
// ObjC implementation of the blockBlock.
expect(lib.getBlockRetainCount(inputBlock.pointer.cast()), 2);

expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (
inputBlock.pointer.cast(),
blockBlock.pointer.cast(),
outputBlock.pointer.cast()
);
expect(blockRetainCount(inputBlock.pointer), 2);

expect(blockRetainCount(blockBlock.pointer), 1);
expect(blockRetainCount(outputBlock.pointer), 1);
return (inputBlock.pointer, blockBlock.pointer, outputBlock.pointer);
}

test('Calling a native block block from Dart has correct ref counting', () {
final (inputBlock, blockBlock, outputBlock) =
nativeBlockBlockDartCallRefCountTest();
doGC();
expect(lib.getBlockRetainCount(inputBlock), 0);
expect(lib.getBlockRetainCount(blockBlock), 0);
expect(lib.getBlockRetainCount(outputBlock), 0);
expect(blockRetainCount(inputBlock), 0);
expect(blockRetainCount(blockBlock), 0);
expect(blockRetainCount(outputBlock), 0);
});

(Pointer<Void>, Pointer<Void>) nativeBlockBlockObjCCallRefCountTest() {
(Pointer<ObjCBlock>, Pointer<ObjCBlock>)
nativeBlockBlockObjCCallRefCountTest() {
final blockBlock = BlockTester.newBlockBlock_(7);
final outputBlock = BlockTester.newBlock_withMult_(blockBlock, 2);
expect(outputBlock(1), 14);
doGC();

expect(lib.getBlockRetainCount(blockBlock.pointer.cast()), 1);
expect(lib.getBlockRetainCount(outputBlock.pointer.cast()), 1);
return (blockBlock.pointer.cast(), outputBlock.pointer.cast());
expect(blockRetainCount(blockBlock.pointer), 1);
expect(blockRetainCount(outputBlock.pointer), 1);
return (blockBlock.pointer, outputBlock.pointer);
}

test('Calling a native block block from ObjC has correct ref counting', () {
final (blockBlock, outputBlock) = nativeBlockBlockObjCCallRefCountTest();
doGC();
expect(lib.getBlockRetainCount(blockBlock), 0);
expect(lib.getBlockRetainCount(outputBlock), 0);
expect(blockRetainCount(blockBlock), 0);
expect(blockRetainCount(outputBlock), 0);
});

(Pointer<Int32>, Pointer<Int32>) objectBlockRefCountTest(Allocator alloc) {
Expand Down
1 change: 0 additions & 1 deletion pkgs/ffigen/test/native_objc_test/static_func_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ output: 'static_func_bindings.dart'
exclude-all-by-default: true
functions:
include:
- getBlockRetainCount
- staticFuncOfObject
- staticFuncOfNullableObject
- staticFuncOfBlock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ exclude-all-by-default: true
ffi-native:
functions:
include:
- getBlockRetainCount
- staticFuncOfObject
- staticFuncOfNullableObject
- staticFuncOfBlock
Expand Down
13 changes: 7 additions & 6 deletions pkgs/ffigen/test/native_objc_test/static_func_native_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:objective_c/objective_c.dart';
import 'package:test/test.dart';

import '../test_utils.dart';
Expand Down Expand Up @@ -82,21 +83,21 @@ void main() {
});
});

Pointer<Void> staticFuncOfBlockRefCountTest() {
Pointer<ObjCBlock> staticFuncOfBlockRefCountTest() {
final block = IntBlock.fromFunction((int x) => 2 * x);
expect(getBlockRetainCount(block.pointer.cast()), 1);
expect(blockRetainCount(block.pointer.cast()), 1);

final outputBlock = staticFuncOfBlock(block);
expect(block, outputBlock);
expect(getBlockRetainCount(block.pointer.cast()), 2);
expect(blockRetainCount(block.pointer.cast()), 2);

return block.pointer.cast();
return block.pointer;
}

test('Blocks passed through static functions have correct ref counts', () {
final (rawBlock) = staticFuncOfBlockRefCountTest();
final rawBlock = staticFuncOfBlockRefCountTest();
doGC();
expect(getBlockRetainCount(rawBlock), 0);
expect(blockRetainCount(rawBlock), 0);
});

Pointer<Int32> staticFuncReturnsRetainedRefCountTest(Allocator alloc) {
Expand Down
11 changes: 6 additions & 5 deletions pkgs/ffigen/test/native_objc_test/static_func_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:objective_c/objective_c.dart';
import 'package:test/test.dart';

import '../test_utils.dart';
Expand Down Expand Up @@ -84,21 +85,21 @@ void main() {
});
});

Pointer<Void> staticFuncOfBlockRefCountTest() {
Pointer<ObjCBlock> staticFuncOfBlockRefCountTest() {
final block = IntBlock.fromFunction((int x) => 2 * x);
expect(lib.getBlockRetainCount(block.pointer.cast()), 1);
expect(blockRetainCount(block.pointer.cast()), 1);

final outputBlock = lib.staticFuncOfBlock(block);
expect(block, outputBlock);
expect(lib.getBlockRetainCount(block.pointer.cast()), 2);
expect(blockRetainCount(block.pointer.cast()), 2);

return block.pointer.cast();
return block.pointer;
}

test('Blocks passed through static functions have correct ref counts', () {
final (rawBlock) = staticFuncOfBlockRefCountTest();
doGC();
expect(lib.getBlockRetainCount(rawBlock), 0);
expect(blockRetainCount(rawBlock), 0);
});

Pointer<Int32> staticFuncReturnsRetainedRefCountTest(Allocator alloc) {
Expand Down
Loading

0 comments on commit b2b0e32

Please sign in to comment.