forked from dart-lang/native
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprotocol_builder_test.dart
More file actions
119 lines (106 loc) · 4.11 KB
/
Copy pathprotocol_builder_test.dart
File metadata and controls
119 lines (106 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Copyright (c) 2024, 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')
library;
import 'package:objective_c/objective_c.dart';
import 'package:objective_c/src/objective_c_bindings_generated.dart'
show ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent;
import 'package:test/test.dart';
import 'util.dart';
// Directly exercises the blockRef extraction pattern from the production fix.
// Extract block.ref into a local `blockRef` (static type ObjCBlockRef, which
// transitively implements Finalizable). The Finalizable contract keeps blockRef
// live in the GC stack map across the non-leaf FFI safepoint below.
//
// Intentionally never-inlined so the JIT performs its own liveness analysis.
@pragma('vm:never-inline')
bool _gcAndCheckBlock() {
final block = ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.fromFunction(
(_, stream, event) {},
keepIsolateAlive: false,
);
final blockRef = block.ref;
final ptr = blockRef.pointer;
// Use callGCNowFromNative (non-leaf safepoint) to trigger GC, then check
// the retain count via blockRetainCount, which reads the block ABI flags
// field — the correct location for block retain counts on all architectures.
callGCNowFromNative();
return blockRetainCount(ptr) > 0;
}
void main() {
group('block wrapper not freed at GC safepoints', () {
setUpAll(() {
initGCInject();
installGCInjectSwizzle();
});
tearDownAll(removeGCInjectSwizzle);
// Diagnostic: verify that gc-now from native code actually triggers GC.
// If this test fails, the GC-injection swizzle is a no-op and the
// reproduction test below is not meaningful.
test('gc-now from native code collects unreachable objects', () {
if (!canDoGC) {
markTestSkipped(
'Dart_ExecuteInternalCommand unavailable — GC injection is a no-op.',
);
return;
}
expect(gcNowAvailableFromNative(), isTrue);
WeakReference<Object>? weakRef;
(() {
final obj = Object();
weakRef = WeakReference(obj);
})();
callGCNowFromNative();
expect(weakRef!.target, isNull);
});
// Swizzle injects gc-now before ObjC retains the block. Without the
// blockRef extraction fix, the optimizer marks `block` dead after its raw
// pointer is extracted and GC drops the retain count to 0.
// Run 1000 iterations to trigger JIT optimisation of implementMethod.
test('block survives GC injected inside implementMethod '
'(fails without blockRef extraction)', () {
const kIterations = 1000;
for (var i = 0; i < kIterations; i++) {
final builder = ObjCProtocolBuilder();
setGCInjectActive(true);
NSStreamDelegate$Builder.stream_handleEvent_.implement(
builder,
(stream, event) {},
);
setGCInjectActive(false);
// wasBlockFreedBeforeRetain() is sticky: stays true once set.
if (wasBlockFreedBeforeRetain()) break;
}
expect(
wasBlockFreedBeforeRetain(),
isFalse,
reason:
'Block was prematurely released by GC before ObjC retained it. '
'blockRef extraction in implementMethod is required (issue #3209).',
);
});
test('block local NOT freed at non-leaf FFI safepoint', () {
// Guaranteed to reproduce on iteration 1 with:
// dart --optimization-counter-threshold=0 test ...
if (!canDoGC) {
markTestSkipped(
'Dart_ExecuteInternalCommand unavailable — gc-now is a no-op, '
'test would pass vacuously.',
);
return;
}
const kIterations = 1000;
for (var i = 0; i < kIterations; i++) {
final survived = _gcAndCheckBlock();
if (!survived) {
fail(
'Block wrapper was GC-collected at FFI safepoint on iteration $i. '
'blockRef extraction in implementMethod required (issue #3209).',
);
}
}
});
});
}